Recent

Author Topic: Constraining types in TFPGObjectlist vs TObjectlist (mode objfpc vs delphi)  (Read 1850 times)

Grew

  • New Member
  • *
  • Posts: 14
I have a unit in {$mode objfpc} that extends the basic functionality of TFPGObjectlist that is then used extensively throughout a program.  I am needing to change it to {$mode delphi} TObjecList as I am unable to traverse the multiple levels of the data to rectify algorithm issues, i.e. TFPGObjectlist leaves me blind after the first level in the data structure. ( I have six levels of lists of items of lists of items....etc.  )
See https://forum.lazarus.freepascal.org/index.php/topic,63486.msg480862.html#msg480862
(In extending my package functionality, a developer changed all my previous mode delphi object lists back to objfpc and then created a single generic object list that he has implemented everywhere.  I took my eye off the ball and when he was complete and gone, I found that I could no longer track my data as before.)

In {$mode objfpc} the class is defined as

Code: Pascal  [Select][+][-]
  1. generic TWTMGenericList<T> = class(specialize TFPGObjectList<TObject>)
  2.  

and then this method works

Code: Pascal  [Select][+][-]
  1. procedure TWTMGenericList.PopulateFrom(aXMLNodeList : TXMLNodeList);
  2. var
  3.   I : Integer;
  4. begin
  5.   Clear;
  6.  
  7.   if Assigned(aXMLNodeList) then
  8.   begin
  9.     for I := 0 to aXMLNodeList.Count - 1 do
  10.       Add(T.Create(aXMLNodeList[I]));
  11.   end;
  12. end;
  13.  

TXMLNodeList is another TFPGObjectList

in {$mode delphi} I have the following

Code: Pascal  [Select][+][-]
  1. TWTMDelphiGenericList<T: class> = class(TObjectList<T>)
  2.  

the "PopulateFrom" method throws a compile error at T.Create( Wrong number of parameters specified for call to "Create")

I am using Lazarus 3.4 with FPC3.2.2.

I have various other errors in these methods as well.

Code: Pascal  [Select][+][-]
  1. function TWTMGenericList.ToJSON : TJSONArray;
  2. var
  3.   I : Integer;
  4. begin
  5.   Result := TJSONArray.Create;
  6.   for I := 0 to Count - 1 do
  7.     Result.Add(Items[I].ToJSON);
  8. end;
  9.  
  10. procedure TWTMGenericList.RemoveLast;
  11. var
  12.   rItem : T;
  13. begin
  14.   rItem := Self.Last;
  15.   Remove(rItem);
  16. end;
  17.  
  18. procedure TWTMGenericList.CopyFrom(aList : specialize TWTMGenericList<T>);
  19. var
  20.   i : Integer;
  21. begin
  22.   if not(Assigned(Self)) then
  23.      Create(true);
  24.  
  25.   Self.Clear;
  26.  
  27.   for i := 0 to aList.Count -1 do
  28.     Self.Add(T.Create(aList[i]));
  29. end;
  30.  
  31. function TWTMGenericList.AddNew : T;
  32. begin
  33.   result := T.Create;
  34.   Self.Add(result);
  35. end;
  36.  

I am wanting to keep both generic object lists in the code and only change  the section of code that I am requiring to see the data.

What specific allows the one mode to resolve T.Create(aXMLNodeList)  but not the other?
Please ask if more info is required.  Thankfull for all assistance in advance.




Thaddy

  • Hero Member
  • *****
  • Posts: 19268
  • Glad to be alive.
What you ask is a bit unclear to me. You can mix and match {$mode objfpc} units and {$mode delphi} units in the same project without problems. type constraints for generics work the same in both modes.

But it may be that what you actually mean is functionality that is only available in fpc trunk/main.
objects are fine constructs. You can even initialize them with constructors.

Grew

  • New Member
  • *
  • Posts: 14
Thank you for your reply Thady. I'll try to simplify. I cannot view multilevel data in the watch window if the data is defined with TFPGObjectList.  However, I can see the multiple levels if they are defined with mode delphi TObjectList. 

Owing to the multilevel of data, the developer employed to extend our app, made a common class derived from TFPGObjectList (full interface section):-
Code: Pascal  [Select][+][-]
  1.   { TWTMGenericList }
  2.  
  3.   generic TWTMGenericList<T> = class(specialize TFPGObjectList<TObject>)
  4.   private
  5.     function Get(Index: Integer): T; {$ifdef FGLINLINE} inline; {$endif}
  6.     procedure Put(Index: Integer; const Item: T); {$ifdef FGLINLINE} inline; {$endif}
  7.     function GetLast: T; {$ifdef FGLINLINE} inline; {$endif}
  8.     procedure SetLast(const Value: T); {$ifdef FGLINLINE} inline; {$endif}
  9.     function GetFirst: T; {$ifdef FGLINLINE} inline; {$endif}
  10.     procedure SetFirst(const Value: T); {$ifdef FGLINLINE} inline; {$endif}
  11.  
  12.   public
  13.     constructor Create(aXMLNodeList : TXMLNodeList); overload;
  14.     constructor Create(aJSON : TJSONArray); overload;
  15.  
  16.     procedure PopulateFrom(aXMLNodeList: TXMLNodeList); overload;
  17.     procedure PopulateFrom(aJSON : TJSONArray); overload;
  18.  
  19.     procedure ToXML(const xmlDoc : TXMLDocument; const parentNode : TDOMNode);
  20.     function ToJSON: TJSONArray;
  21.  
  22.     {Removes last item.}
  23.     procedure RemoveLast;
  24.     {Copies from another list.}
  25.     procedure CopyFrom(aList : specialize TWTMGenericList<T>);
  26.     {Adds a new empty item to the list and returns it.}
  27.     function AddNew: T;
  28.     function Extract(const Item : T): T; {$ifdef FGLINLINE} inline; {$endif}
  29.  
  30.     property Items[Index: Integer]: T read Get write Put; default;
  31.     property First: T read GetFirst write SetFirst;
  32.     property Last: T read GetLast write SetLast;
  33.   end;                                          
  34.  

I need this class to be of {$mode Delphi}, using TObjectList.   So I created a new unit and copied the code across and started refactoring it.

Code: Pascal  [Select][+][-]
  1.   TWTMDelphiGenericList<T: TAbstractWTM> = class(TObjectList<T>)
  2.   private
  3.  
  4.   public
  5.     constructor Create(aXMLNodeList: TXMLNodeList); overload;
  6.     constructor Create(aJSON: TJSONArray); overload;
  7.  
  8.     procedure PopulateFrom(aXMLNodeList: TXMLNodeList); overload;
  9.     procedure PopulateFrom(aJSON: TJSONArray); overload;
  10.  
  11.     procedure ToXML(const xmlDoc: TXMLDocument; const parentNode: TDOMNode);
  12.     function ToJSON: TJSONArray;
  13.  
  14.     procedure RemoveLast;
  15.     procedure CopyFrom(aList: TWTMDelphiGenericList<T>);
  16.     function AddNew: T;
  17.     function Extract(const Item: T): T; inline;
  18.   end;
  19.  

So comparing my declaration from the previous post of early this morning, I have added the constraint to the abstract class <T: TAbstractWTM> which currently consists of

Code: Pascal  [Select][+][-]
  1. unit uAbstractWTM;
  2.  
  3. {$mode ObjFPC}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils,  DOM, WTXMLUtil, fpjson;
  9. type
  10.  
  11. // Abstract base class for the generic list
  12.  
  13.   { TAbstractWTM }
  14.  
  15.   TAbstractWTM = class
  16.   public
  17.     constructor Create ();virtual; abstract;
  18.     constructor Create(aXMLNode: TDOMNode); virtual; abstract;
  19.     constructor Create(aJSON: TJSONObject); virtual; abstract;
  20.     function ToXML(const xmlDoc: TXMLDocument): TDOMNode; virtual; abstract;
  21.     function ToJSON: TJSONObject; virtual; abstract;
  22.  
  23.   end;
  24.  
  25. implementation
  26.  
  27. end.
  28.  

This constraint seems to have stopped the majority of the errors that it was showing, except one.  In the CopyFrom method the passed in parameter is of the type of the class itself, TWTMDelphiGenericList.  Obviously, that is excluded in the Abstract class I made, but this does show to me that it is not handling constraints in the same manner as TFPGObjectList.

I am beyond the depth of my knowledge in this so unsure if I have described this fully technically correctly.

Thaddy

  • Hero Member
  • *****
  • Posts: 19268
  • Glad to be alive.
Thank you for your reply Thady. I'll try to simplify. I cannot view multilevel data in the watch window if the data is defined with TFPGObjectList.  However, I can see the multiple levels if they are defined with mode delphi TObjectList. 
I see, you are mixing up TObjectList from classes with the generic object list from FGL.
TObjectlist from classes has the same interface in both Delphi and FPC.
The FGL, however, is FPC only.
I suggest to use the generics.collections.TObjectlist<>, which is also generic and is compatible with both Delphi and Freepascal because, again, it has the same interface.
Quote
Owing to the multilevel of data, the developer employed to extend our app, made a common class derived from TFPGObjectList (full interface section):-
As above: that's where it goes wrong: use the generic TObjectlist<> from generics.collections, because that is not likely to cause probems. Choosing the FGL's TFPGObjectList  is a big mistake in this case.
Actually, that developer should solve that free of charge, since you explicitly required to have the code compile in both Delphi and Freepascal, so it is his fault.
(I forgive you that you mentioned mode, where you actually meant the two distinct compilers.)

The developer you chose does not have enough experience to work synchronized in both languages and keep everything compatible and compileable in both languages from the same sources.
Or is lazy and makes bad choices.

BTW: it is a one hour fix... probably less..
« Last Edit: January 27, 2025, 03:58:18 pm by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

Grew

  • New Member
  • *
  • Posts: 14
Thank you Thaddy.

When you know, it takes an hour to fix.  When you don't, it takes days of trawling the internet to find out how it all works first. :D

I seem to be on the right track here at last. Adding the abstract class and constraining the new generic class that was descended from TObjectList<T> seems to have done the trick.  This is what I have at the moment

Code: Pascal  [Select][+][-]
  1.   TAbstractWTMserial = class
  2.     public
  3.       constructor Create; overload; virtual; abstract;
  4.       constructor Create(aItem : TObject);overload; virtual; abstract;
  5.       constructor Create(aXMLNode: TDOMNode); overload; virtual; abstract;
  6.       constructor Create(aJSONObject: TJSONObject); overload; virtual; abstract;
  7.       function ToXML(const xmlDoc: TXMLDocument): TDOMNode; virtual; abstract;
  8.       function ToJSON: TJSONObject; virtual; abstract;
  9.       procedure CopyFrom(aSource: TAbstractWTMserial); virtual; abstract;
  10.     end;
  11.  

and the the type declaration like

Code: Pascal  [Select][+][-]
  1.   TWTMDelphiGenericList<T: TAbstractWTMserial, constructor> = class(TObjectList<T>)
  2.  

The concrete classes are then descended from the Abstract one.  Busy modifying them for this now.  Still hope I get it right and no other major issues show up.

Honestly, close to impossible to find anyone willing to touch Pascal these days. But I am learning, and now much, much faster with the help of AI. ( when it is not hallucinating) :)

Thaddy

  • Hero Member
  • *****
  • Posts: 19268
  • Glad to be alive.
I think you are still not on the righ track:
Instead of uses fgl, you do uses generics.collections;
And the whole point of using generics is that you do not need inheritance as such, but merely declaration, either as type or var. I will give you a simple example
Code: Pascal  [Select][+][-]
  1. {$ifdef fpc}{$mode delphi}{$endif}// add this to all units
  2. uses generics.collections;
  3. type
  4.    TListOfStringlists = TObjectList<TStringlist>;// not inheritance, but specialization
  5.    // add more specialized lists here..
  6. var
  7.   Lists:TListOfStringlists;
  8. begin
  9.   Lists :=  TListOfStringlists.Create;// the list owns the objects by default;
  10.  
The point is, that even if it is possible to use inheritance here - and sometimes even a good pattern - it is usually not necessary when using generics.

The one hour "fix" is simply to change the uses clause and add the ifdef to your unit(s): the code is then 100% compatible between the delphi and fpc compilers, maybe with another ifdef for the dotted unit scoping in delphi.(not necessary in trunk/main with rtl compiled for dotted units).
So get rid of the fgl, use generics.collections instead and your initial code should already work. Don't overcomplicate things. The rest is up to you. Should even take less than an hour.

Note that if your code uses unicode16 strings, the default in delphi, use {$mode delphiunicode}. In fpc trunk/main there is also a unicode rtl version.
Translating delphi code to objfpc mode is a waste of time in any but the most rarest of cases.
For new - clean - non-delphiprojects just in freepascal it can have some benefits in the sense objfpc mode is more strict, but also more verbose. There are some constructs that are not possible in delphi.
« Last Edit: January 28, 2025, 08:11:17 am by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

Grew

  • New Member
  • *
  • Posts: 14
Quote
So get rid of the fgl, use generics.collections instead and your initial code should already work. Don't overcomplicate things.

I think I just had an Aha! moment here.  So probably, the TObjectList was not constraining to my concrete classes, because those were still {$mode objfpc} and I had only changed the TObjectList unit to {$mode Delphi}?  However, not sure how to explain why the "copy from" method threw an error not recognising itself though?  It could have still being affecting it I suppose?

Code: Pascal  [Select][+][-]
  1. procedure CopyFrom(aList: TWTMDelphiGenericList<T>);
  2.  

I did learn a lot about abstract classes, interfaces and the generics in the process, so not all a waste, I suppose.  I would like to sometime set up a small project that better represents the whole projects data structure for better understanding, standardisation and testing.

Anyway, once I had a mechanism to "look under the hood" once more, I threw my effort into "tuning the engine" and fixing the errors which were the reason I had to go down the path in the first place.

 Very grateful for your feedback Thaddy.  Can't tell you what it means to a newby.

(btw, to be fair to the developer, his primary language was Java.  This was really a large change in our application.  What was two programs, one run on an office pc, and the second run in a factory environment to preset machining positions, information previously being passed in XML files, he extended the factory one with the functionality of the office one, bringing in mORMot as a database for project management as well.  The original code was really not super clean, written mostly by us mechanical type who know "what the machines need to do".  I think he tried his best to bring is some good practices, but unfortantely left me completely in the dark to fix anything when it went wrong.  But he was distinctly instructed that he needs to work in {$mode Delphi} for generics ( which was ignored.)

 

TinyPortal © 2005-2018