Initialization Pattern?
Initialization Pattern?
type
TIdentification = (Foo, Bar, Fubar, Snark, Plugh, Plover);
TAssociation = (Ninja, Cowboy, Pirate);
TIdAssoArray = array[TIdentification] of TAssociation;
var // or const
IdentityToType : TIdAssoArray;
What is the best way of initializing IdentityToType so that it is robust against adding, removing or reordering the TIdentity members?
type
TIdentification = (Foo, Bar, Fubar, Snark, Plugh, Plover);
TAssociation = (Ninja, Cowboy, Pirate);
TIdAssoArray = array[TIdentification] of TAssociation;
var // or const
IdentityToType : TIdAssoArray;
What is the best way of initializing IdentityToType so that it is robust against adding, removing or reordering the TIdentity members?
Actually the most robust way is not having an array at all but a list with records/classes that contain TIdentification and TAssociation and a check procedure that is called from the initialization section.
ReplyDeleteNot the best performance though.
I'm currently doing a loop for t := low(TIdentification) to high(TIdentification) and a case statement, but it's getting harder to maintain, as the number of ids and associations goes up.
ReplyDeleteDeclaring IdentityToType as const is only robust against adding/removing but not to reordering TIdentification. But for me that is usually good enough - you might put a comment there saying that if changing the order you must modify the const.
ReplyDeleteIs the association and mapping tightly coupled or is it actually somewhere else in the code?
ReplyDeleteIf you persist the TIdAssoArray, you will need some RTTI verification coupled with the TIdentification item names.
ReplyDeleteAsbjørn Heid - The intent is to have it tightly coupled.
ReplyDeleteI wish it would have been possible to have a construct similar to
const
IdentityToType = TIdAssoArray(
(Foo: Ninja),
(Bar: Pirate),
(Fubar: Ninja),
(Snark: Pirate),
(Plugh: Ninja),
(Plover: Cowboy) );
which would complain on missing, duplicate or unknown elements.
Lars Fosdal Hm yes, I had some ideas on the tram but didn't pan out.
ReplyDeleteTo check the pattern at runtime, use:
ReplyDeletefunction Check_TIdentification: Boolean;
var
tid: TIdentification;
const
tidName : array[TIdentification] of String = (
'Foo',
'Bar',
'Fubar',
'Snark',
'Plugh',
'Plover');
begin
for tid := Low(TIdentification) to High(TIdentification) do
begin
if (GetEnumName(TypeInfo(TIdentification),Ord(tid)) <> tidName[tid]) then
Exit(False);
end;
Result := true;
end;
This will catch adding,removing and reordering.
Leif Uneus And why does that require the enum name? ;)
ReplyDeletefunction Check_TIdentification: Boolean;
const
IdValues: array[TIdentification] of TIdentification = (
Foo, Bar, Fubar, Snark, Plugh, Plover);
var
id: TIdentification;
begin
for id := Low(TIdentification) to High(TIdentification) do
if id <> IdValues[id] then
Exit(False);
Result := true;
end;
Leif Uneus - No, no and no. That's introducing yet another order that needs to be maintained. Here is what I do now.
ReplyDeleteprocedure InitIdentities;
var
t: TIdentification;
begin
for t := Low(IdentityToType) to (High(IdentityToType)
do Case t of
Foo,
Fubar,
Plugh: IdentityToType[t] := Ninja;
Bar,
Snark: IdentityToType[t] := Pirate;
Plover: IdentityToType[t] := Cowboy;
else raise EIdentificationConfigError.Create('Missing init for ' + GetEnumName(TypeInfo(TIdentification), Ord(t)));
end;
end;
Which will detect missing inits at first try - but - I'd rather have that check done at compile time, and have the Identity and Association relationships clearly defined in the source. The above is a bit messy when you start running into larger numbers of Identities and Associations.
Personally I hate wasting my time writing code to prevent stupid programmers from breaking anything.
ReplyDeleteA comment stating that you have to reorder the const array when you change the order of the enum should suffice. And for any mistakes: commit history/blame is your friend.
What if you have a multi-dimensional matrix?
ReplyDeleteCan't they have comments?
ReplyDeleteSure, but imagine manually changing the order of 50+ submatrixes... That's a rather error prone task.
ReplyDeleteI guess I just want constant array declarations to be smarter.
Ok, here is another alternative:
ReplyDeleteprocedure InitAsso(var asso: TIdAssoArray);
Type
TIdAssoRec = record
id : TIdentification;
asso: TAssociation;
end;
const
IdAssoRecArr : array[TIdentification] of TIdAssoRec =
((id:Foo; asso:Ninja),
(id:Bar; asso:Ninja),
(id:Fubar; asso:Cowboy),
(id:Snark; asso:Pirate),
(id:Plugh; asso:Pirate),
(id:Plover;asso:Cowboy));
var
tid: TIdentification;
begin
for tid := Low(TIdentification) to High(TIdentification) do
begin
if (tid <> IdAssoRecArr[tid].id) then
raise Hell;
asso[tid] := IdAssoRecArr[tid].asso;
end;
end;
Perhaps easier to maintain, but still ugly and still a runtime (mostly) check.
Stefan Glienke Yes, it could have been done without strings. My mind was set on next step where perhaps the array would have to be stored and must handle reordering. I guess using enumerations here is a bit of an antipattern for large enumeration items.
ReplyDeleteYou could also do this:
ReplyDeleteType
TIdAssoRec = record
private
id : TIdentification;
asso: TAssociation;
class function Init(i: TIdentification;a:TAssociation): TIdAssoRec; static;
public
class procedure InitAsso(var asso: TIdAssoArray); static;
end;
class function TIdAssoRec.Init(i: TIdentification; a: TAssociation): TIdAssoRec;
begin
Result.id := i;
Result.asso := a;
end;
class procedure TIdAssoRec.InitAsso(var asso: TIdAssoArray);
var
IdAssoRecArr: TArray;
tid: TIdentification;
begin
IdAssoRecArr :=
[Init(Foo, Ninja),
Init(Bar, Ninja),
Init(Fubar, Cowboy),
Init(Snark, Pirate),
Init(Plugh, Pirate),
Init(Plover, Cowboy)];
if (High(IdAssoRecArr) <> Ord(High(TIdentification))) then
raise Hell;
for tid := Low(TIdentification) to High(TIdentification) do
begin
if (tid <> IdAssoRecArr[Ord(tid)].id) then
raise Hell;
asso[tid] := IdAssoRecArr[Ord(tid)].asso;
end;
end;