Recent

Author Topic: Generic Generic list  (Read 12011 times)

Edson

  • Hero Member
  • *****
  • Posts: 1301
Generic Generic list
« on: February 24, 2017, 09:19:44 pm »
I have this:
Code: Pascal  [Select][+][-]
  1.  
  2.   TA = class ...
  3.  
  4.   TA1 = class(TA) ...
  5.   TA1_list = specialize TFPGObjectList<TA1>;  
  6.  
  7.   TA2 = class(TA) ...
  8.   TA2_list = specialize TFPGObjectList<TA2>;  
  9.  
  10.    list1: TA1_list;
  11.    list2: TA2_list
  12.  

list1 and list2 are filled with items;
Then I need to iterate through list1 or list2, to access methods of TA. I have done this:

Code: Pascal  [Select][+][-]
  1.   TA_list = specialize TFPGObjectList<TA>;  
  2.   list: TA_list;
  3.   list := TA_list(list1);   //WARNING: class types not related
  4.   for item in list do ...
  5.  

This seems to work, but looks some weird  %)
I'm sure there are better ways. Suggestions?
Lazarus 2.2.6 - FPC 3.2.2 - x86_64-win64 on Windows 10

ASerge

  • Hero Member
  • *****
  • Posts: 2223
Re: Generic Generic list
« Reply #1 on: February 25, 2017, 02:40:16 pm »
How about that:
Code: Pascal  [Select][+][-]
  1. for item in list1 do ...; for item in list2 do ...;

Edson

  • Hero Member
  • *****
  • Posts: 1301
Re: Generic Generic list
« Reply #2 on: February 25, 2017, 04:28:43 pm »
It would be easy, but there are more than two list. And the quantity will be increasing.

I need some kind of ancestor for all this generic list, to use for a general iteration.

Until now, my solution:

Code: Pascal  [Select][+][-]
  1.   TA_list = specialize TFPGObjectList<TA>;  
  2.   list: TA_list;
  3.   list := TA_list(list1);   //WARNING: class types not related
  4.   for item in list do ...

is working, but I have the feeling, this is not the best way to face the problem.
Lazarus 2.2.6 - FPC 3.2.2 - x86_64-win64 on Windows 10

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11383
  • FPC developer.
Re: Generic Generic list
« Reply #3 on: February 25, 2017, 04:41:22 pm »
I have also encountered similar problems in Delphi, and used a typecast, which always went fine. Usually to pass an array of a descendent type to a method that accepted an array of the parent type.

It is not entirely typesafe, but IMHO the risk is fairly low since the size of elements and everything are the same. So basically the only difference is the type reference in whatever the dynamic array descriptor is.

Edson

  • Hero Member
  • *****
  • Posts: 1301
Re: Generic Generic list
« Reply #4 on: February 25, 2017, 05:34:30 pm »
Thanks @markov
That's what I wanted to hear. I think it's not typesafe, too, but it works well.

I will be implementing this method in my design, unless I see there are collateral effects.
Lazarus 2.2.6 - FPC 3.2.2 - x86_64-win64 on Windows 10

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11383
  • FPC developer.
Re: Generic Generic list
« Reply #5 on: February 25, 2017, 08:28:16 pm »
Of course when you go to a non-native backend (LLVM,JVM) then all bets are off.

Thaddy

  • Hero Member
  • *****
  • Posts: 14205
  • Probably until I exterminate Putin.
Re: Generic Generic list
« Reply #6 on: February 25, 2017, 08:41:34 pm »
Hmm. That's a bit of nonsense since there are jvm native processors for a very, very long time. (mainly smart cards. jvm implemented in hardware. E.g. Gemplus https://en.wikipedia.org/wiki/Java_Card).

Anyway. My gut feeling says this could be massaged into something like:
Code: Pascal  [Select][+][-]
  1. for item<T> in list<T> do..
I know it doesn't work..yet.
« Last Edit: February 25, 2017, 08:49:19 pm by Thaddy »
Specialize a type, not a var.

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11383
  • FPC developer.
Re: Generic Generic list
« Reply #7 on: February 25, 2017, 08:55:17 pm »
Hmm. That's a bit of nonsense since there are jvm native processors for a very, very long time. (mainly smart cards. jvm implemented in hardware. E.g. Gemplus https://en.wikipedia.org/wiki/Java_Card).

(LLVM is also not the native backend, yet can generate native code, you seem to confuse native backend (as in FPC's backend) with native code)

The point is that the type system of such systems is potentially totally different, making the cast dangerous or invalid

ASerge

  • Hero Member
  • *****
  • Posts: 2223
Re: Generic Generic list
« Reply #8 on: February 25, 2017, 11:26:56 pm »
It would be easy, but there are more than two list. And the quantity will be increasing.
Do you really need TA_list1, TA_list2, ...? TA_list can hold any TA1, TA2, ... instances.

Edson

  • Hero Member
  • *****
  • Posts: 1301
Re: Generic Generic list
« Reply #9 on: February 26, 2017, 03:17:44 am »
Do you really need TA_list1, TA_list2, ...? TA_list can hold any TA1, TA2, ... instances.

Yes, I know TA_list can hold TA1, TA2, ... However, I have differentes objects, each one with its own list ( TA1_list, TA2_list, ...) and I need to pass all this lists, like parameters of a method.
Lazarus 2.2.6 - FPC 3.2.2 - x86_64-win64 on Windows 10

ASerge

  • Hero Member
  • *****
  • Posts: 2223
Re: Generic Generic list
« Reply #10 on: February 26, 2017, 08:05:22 pm »
Well, if there are many types of lists, it may be a lot of procedures?
Code: Pascal  [Select][+][-]
  1. {$APPTYPE CONSOLE}
  2. program Project1;
  3.  
  4. uses fgl;
  5.  
  6. type
  7.   TA = class(TObject)
  8.     procedure Print;
  9.   end;
  10.  
  11.   TA1 = class(TA);
  12.   TA2 = class(TA);
  13.  
  14.   generic TGMyListWithProc<T: TA> = class(TObject)
  15.   public
  16.     type
  17.       TList = specialize TFPGObjectList<T>;
  18.     class procedure ForEachPrint(List: TList); static;
  19.   end;
  20.  
  21.   TMyList1 = specialize TGMyListWithProc<TA1>;
  22.   TMyList2 = specialize TGMyListWithProc<TA2>;
  23.  
  24. procedure TA.Print;
  25. begin
  26.   Writeln(ClassName);
  27. end;
  28.  
  29. class procedure TGMyListWithProc.ForEachPrint(List: TList);
  30. var
  31.   Item: T;
  32. begin
  33.   for Item in List do
  34.     Item.Print;
  35. end;
  36.  
  37. var
  38.   List1: TMyList1.TList;
  39.   List2: TMyList2.TList;
  40. begin
  41.   List1 := TMyList1.TList.Create;
  42.   try
  43.     List1.Add(TA1.Create);
  44.     TMyList1.ForEachPrint(List1);
  45.   finally
  46.     List1.Free;
  47.   end;
  48.   List2 := TMyList2.TList.Create;
  49.   try
  50.     List2.Add(TA2.Create);
  51.     TMyList2.ForEachPrint(List2);
  52.   finally
  53.     List2.Free;
  54.   end;
  55.   ReadLn;
  56. end.

Edson

  • Hero Member
  • *****
  • Posts: 1301
Re: Generic Generic list
« Reply #11 on: March 01, 2017, 04:22:14 pm »
Thanks @ASerge.

Well, if there are many types of lists, it may be a lot of procedures?
Code: Pascal  [Select][+][-]
  1. {$APPTYPE CONSOLE}
  2. program Project1;
  3.  
  4. uses fgl;
  5.  
  6. type
  7.   TA = class(TObject)
  8.     procedure Print;
  9.   end;
  10.  
  11.   TA1 = class(TA);
  12.   TA2 = class(TA);
  13.  
  14.   generic TGMyListWithProc<T: TA> = class(TObject)
  15.   public
  16.     type
  17.       TList = specialize TFPGObjectList<T>;
  18.     class procedure ForEachPrint(List: TList); static;
  19.   end;
  20.  
  21.   TMyList1 = specialize TGMyListWithProc<TA1>;
  22.   TMyList2 = specialize TGMyListWithProc<TA2>;
  23.  
  24. procedure TA.Print;
  25. begin
  26.   Writeln(ClassName);
  27. end;
  28.  
  29. class procedure TGMyListWithProc.ForEachPrint(List: TList);
  30. var
  31.   Item: T;
  32. begin
  33.   for Item in List do
  34.     Item.Print;
  35. end;
  36.  
  37. var
  38.   List1: TMyList1.TList;
  39.   List2: TMyList2.TList;
  40. begin
  41.   List1 := TMyList1.TList.Create;
  42.   try
  43.     List1.Add(TA1.Create);
  44.     TMyList1.ForEachPrint(List1);
  45.   finally
  46.     List1.Free;
  47.   end;
  48.   List2 := TMyList2.TList.Create;
  49.   try
  50.     List2.Add(TA2.Create);
  51.     TMyList2.ForEachPrint(List2);
  52.   finally
  53.     List2.Free;
  54.   end;
  55.   ReadLn;
  56. end.

What I see this is creating two different classes and two different iteration code (although reusing the code  :)).

I was thinking on using just one routine (will be part of a class), that can accept different generic list:

Code: Pascal  [Select][+][-]
  1. program Project1;
  2. uses fgl;
  3. type
  4.   TA = class(TObject)
  5.     procedure Print;
  6.   end;
  7.  
  8.   TA1 = class(TA);
  9.   TA2 = class(TA);
  10.  
  11.   TMyList1 = specialize TFPGObjectList<TA1>;
  12.   TMyList2 = specialize TFPGObjectList<TA2>;
  13.  
  14.   TMyList = specialize TFPGObjectList<TA>;
  15.  
  16. procedure DoSomethingWithAnyList(list: TMyList);
  17. var Item: TA;
  18. begin
  19.   for Item in List do
  20.     Item.Print;
  21. end;
  22.  
  23. procedure TA.Print;
  24. begin
  25.   Writeln(ClassName);
  26. end;
  27.  
  28. var
  29.   List1: TMyList1;
  30.   List2: TMyList2;
  31. begin
  32.   List1 := TMyList1.Create;
  33.   List1.Add(TA1.Create);
  34.  
  35.   List2 := TMyList2.Create;
  36.   List2.Add(TA2.Create);
  37.  
  38.   DoSomethingWithAnyList(TMyList(List1));  //Warning
  39.   DoSomethingWithAnyList(TMyList(List2));  //Warning
  40.  
  41.   ReadLn;
  42. end.

Something like this, but without warnings  :o.

Lazarus 2.2.6 - FPC 3.2.2 - x86_64-win64 on Windows 10

Thaddy

  • Hero Member
  • *****
  • Posts: 14205
  • Probably until I exterminate Putin.
Re: Generic Generic list
« Reply #12 on: March 01, 2017, 04:33:49 pm »
As long as you try to hard cast.....
" DoSomethingWithAnyList(TMyList(List1));  //Warning"
How can the compiler tell anything else than a warning?.....
Actually that is already more than I expected.

What the compiler says to you is: "I know you are probably stupid... I do what you want... but... if I do it wrong it is your fault."
A hard cast bypasses the type checks.... Listen to what the compiler says...
« Last Edit: March 01, 2017, 04:38:50 pm by Thaddy »
Specialize a type, not a var.

Edson

  • Hero Member
  • *****
  • Posts: 1301
Re: Generic Generic list
« Reply #13 on: March 02, 2017, 05:15:02 pm »
Well, I don`t complaint about the compiler message. It is very useful.

I just was asking if there is some other way for solve this problem (a way to process many generic lists), without disturbing the compiler.   :)
Lazarus 2.2.6 - FPC 3.2.2 - x86_64-win64 on Windows 10

furious programming

  • Hero Member
  • *****
  • Posts: 853
Re: Generic Generic list
« Reply #14 on: February 02, 2019, 12:10:04 am »
Forgive me for digging up the thread, but I had the same problem and solved it in a fairly simple way.


We have a base type that has universal data for all inheriting classes, for example:

Code: Pascal  [Select][+][-]
  1. type
  2.   TThing = class(TObject)
  3.   private
  4.     FName: String;
  5.   public
  6.     constructor Create(); virtual; abstract;
  7.   public
  8.     property Name: String read FName;
  9.   end;

and the type of a generic list of such objects:

Code: Pascal  [Select][+][-]
  1. type
  2.   TThings = specialize TFPGObjectList<TThing>;

They inherit two classes from the base class, for example:

Code: Pascal  [Select][+][-]
  1. type
  2.   TSmallThing = class(TThing)
  3.   public
  4.     constructor Create(); override; // determines the value of the FName field
  5.   end;
  6.  
  7. type
  8.   TBigThing = class(TThing)
  9.   public
  10.     constructor Create(); override; // determines the value of the FName field
  11.   end;

Each of them has a dedicated type of generic list, because it is required:

Code: Pascal  [Select][+][-]
  1. type
  2.   TSmallThings = specialize TFPGObjectList<TSmallThing>;
  3.   TBigThings   = specialize TFPGObjectList<TBigThing>;

Now we need a routine, which has in the parameter to retrieve any list containing objects inheriting from the base class (TSmallThings or TBigThings) and which only operates on data from the base class (here: uses the Name property). If we declare a parameter as a list with objects of the base class:

Code: Pascal  [Select][+][-]
  1. procedure ShowThingNames(AThings: TThings);
  2. var
  3.   Thing: TThing;
  4. begin
  5.   for Thing in AThings do
  6.     WriteLn(Thing.Name);
  7.  
  8.   WriteLn();
  9. end;

and we will provide a list of the type containing end-class objects, a compilation error related to type mismatches will occur::

Code: Pascal  [Select][+][-]
  1. var
  2.   SmallThings: TSmallThings;
  3. begin
  4.   SmallThings := TSmallThings.Create();
  5.   ShowThingNames(SmallThings); // Error: Incompatible type

To be able to compile the code, we can use casting (it will work properly), but we will get a warning:

Code: Pascal  [Select][+][-]
  1. var
  2.   SmallThings: TSmallThings;
  3. begin
  4.   SmallThings := TSmallThings.Create();
  5.   ShowThingNames(TThings(SmallThings)); // Warning: Class types are not related

The simplest solution, which allows to deceive the compiler and not force it to check types (and by the way, to display warnings), is to use the untyped parameters and absolute:

Code: Pascal  [Select][+][-]
  1. procedure ShowThingNames(const AThings);
  2. var
  3.   Things: TThings absolute AThings;
  4.   Thing: TThing;
  5. begin
  6.   for Thing in Things do
  7.     WriteLn(Thing.Name);
  8.  
  9.   WriteLn();
  10. end;

Thanks to this, the code will compile without errors and warnings, and we can pass to the routine a list of any objects inheriting from the base class:

Code: Pascal  [Select][+][-]
  1. var
  2.   SmallThings: TSmallThings;
  3.   BigThings: TBigThings;
  4. begin
  5.   SmallThings := TSmallThings.Create();
  6.   BigThings := TBigThings.Create();
  7.   try
  8.     SmallThings.Add(TSmallThing.Create());
  9.     SmallThings.Add(TSmallThing.Create());
  10.  
  11.     BigThings.Add(TBigThing.Create());
  12.     BigThings.Add(TBigThing.Create());
  13.  
  14.     ShowThingNames(SmallThings);
  15.     ShowThingNames(BigThings);
  16.   finally
  17.     SmallThings.Free();
  18.     BigThings.Free();
  19.   end;
  20. end.

The downside of this solution is the lack of control of the data types provided in the parameter — this is not a safe solution. Therefore, you must use such routines with your head, taking care of correctness on your own.

Sources of the test project in the attachment.
« Last Edit: February 02, 2019, 12:18:14 am by furious programming »
Lazarus 3.2 with FPC 3.2.2, Windows 10 — all 64-bit

Working solo on an acrade, action/adventure game in retro style (pixelart), programming the engine and shell from scratch, using Free Pascal and SDL. Release planned in 2026.

 

TinyPortal © 2005-2018