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?

Comments

  1. 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.
    Not the best performance though.

    ReplyDelete
  2. 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.

    ReplyDelete
  3. Declaring 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.

    ReplyDelete
  4. Is the association and mapping tightly coupled or is it actually somewhere else in the code?

    ReplyDelete
  5. If you persist the TIdAssoArray, you will need some RTTI verification coupled with the TIdentification item names.

    ReplyDelete
  6. Asbjørn Heid - The intent is to have it tightly coupled. 

    I 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.

    ReplyDelete
  7. Lars Fosdal Hm yes, I had some ideas on the tram but didn't pan out.

    ReplyDelete
  8. To check the pattern at runtime, use:

    function 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.

    ReplyDelete
  9. Leif Uneus And why does that require the enum name? ;)

    function 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;

    ReplyDelete
  10. Leif Uneus - No, no and no.  That's introducing yet another order that needs to be maintained.  Here is what I do now.

    procedure 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.

    ReplyDelete
  11. Personally I hate wasting my time writing code to prevent stupid programmers from breaking anything.

    A 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.

    ReplyDelete
  12. What if you have a multi-dimensional matrix?

    ReplyDelete
  13. Sure, but imagine manually changing the order of 50+ submatrixes... That's a rather error prone task.  

    I guess I just want constant array declarations to be smarter.

    ReplyDelete
  14. Ok, here is another alternative:

    procedure 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.

    ReplyDelete
  15. 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.

    ReplyDelete
  16. You could also do this:

    Type
      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;

    ReplyDelete

Post a Comment