Recent

Author Topic: Generics typecast for parameterized type descendant  (Read 2045 times)

daniel_sap

  • Jr. Member
  • **
  • Posts: 64
Generics typecast for parameterized type descendant
« on: February 19, 2024, 12:06:54 pm »
Hi,

Recently I use more actively free pascal and I noticed that I have to do typecasting very often when using parameterized type descendants in generics

Code: Pascal  [Select][+][-]
  1.  
  2. TMyClass.startTheList();
  3. var
  4.   myList: TList<TMyClass>;
  5. begin
  6.   myList := TList<TMyClass>.Create();
  7.   doSomething(myList);  // the compiler forces me to do the typecast doSomething(TList<TObject>(myList));
  8. end;
  9.  
  10. TMyClass.doSomething(myVar: TList<TObject>);
  11. begin
  12.   // some code using the list
  13. end;
  14.  
  15.  

For me it looks good to pass TList<TMyClass> when TList<TObject> is needed cause TMyClass is descendant of TObject and can act as TObject.
Even more passing TMyList<TMyClass> when TList<TObject> should be fine, without typecasting.
It looks a little bit to me like compiler forces me to typecast when I assign descendant class variable to parent class variable
Code: Pascal  [Select][+][-]
  1.  
  2. TMyClass.do();
  3. var
  4.   obj: TObject;
  5.   myObj: TMyClass;
  6. begin
  7.   obj := TObject(myObj);
  8. end;
  9.  
  10.  

Do you know if such feature is planned for implementation.
Does it look good for you to be implemented.
Do you see some issues in implementing this

(in C# I think you cannot even do the typecast. In Java allows me to do it fine)


cdbc

  • Hero Member
  • *****
  • Posts: 1786
    • http://www.cdbc.dk
Re: Generics typecast for parameterized type descendant
« Reply #1 on: February 19, 2024, 12:38:54 pm »
Hi
Have you tried {$mode delphi} ?!?
The 2 modes 'objpas' & 'delphi' behave differently wrt. generics.
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 2.2.6 up until Jan 2024 from then on it's: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 3.0

Thaddy

  • Hero Member
  • *****
  • Posts: 16409
  • Censorship about opinions does not belong here.
Re: Generics typecast for parameterized type descendant
« Reply #2 on: February 19, 2024, 01:07:20 pm »
Like so?
Code: Pascal  [Select][+][-]
  1. {$mode delphi}
  2. uses generics.collections;
  3. type
  4. TMyclass = class;//forward
  5. TMyList = TList<TMyClass>; //declare a type does the trick
  6.  
  7. TmyClass = class
  8. private
  9.   MyList:TMyList;
  10. public
  11.   procedure doSomething;
  12.   constructor Create;
  13.   destructor Destroy;override;
  14. end;
  15.  
  16. constructor TMyClass.Create;
  17. begin
  18.   inherited create;
  19.   myList := TMyList.Create;
  20. end;
  21.  
  22. destructor TMyClass.Destroy;
  23. begin
  24.   myList.Free;
  25.   inherited;
  26. end;
  27.  
  28. procedure TMyClass.doSomething;
  29. begin
  30.   // some code using the list
  31. end;
  32.  
  33. var
  34.   myClass:TMyClass;
  35. begin
  36.   myClass:=TMyClass.Create;
  37.   myClass.DoSomething;
  38.   myClass.Free;
  39. end.

Specializing a type prevents the need for typecasting a variable all the time.
This example has no typecasts at all.. And requires no further specializations.

In mode objfpc you have to add one (1) word:
Code: Pascal  [Select][+][-]
  1. type
  2. .....
  3.   TMyList = specialize TList<TMyClass>;
Rest is the same.
Although there are maybe use cases for specializing on a var I have not seen a convincing example. Specialize on a type.

« Last Edit: February 19, 2024, 02:37:04 pm by Thaddy »
There is nothing wrong with being blunt. At a minimum it is also honest.

cdbc

  • Hero Member
  • *****
  • Posts: 1786
    • http://www.cdbc.dk
Re: Generics typecast for parameterized type descendant
« Reply #3 on: February 19, 2024, 03:47:32 pm »
Hi
@Thaddy: Thanks mate, something like that was on my mind, but I couldn't explain it as well, as you just did  :-X
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE5 -> FPC 3.2.2 -> Lazarus 2.2.6 up until Jan 2024 from then on it's: KDE5/QT5 -> FPC 3.3.1 -> Lazarus 3.0

Thaddy

  • Hero Member
  • *****
  • Posts: 16409
  • Censorship about opinions does not belong here.
Re: Generics typecast for parameterized type descendant
« Reply #4 on: February 19, 2024, 04:43:29 pm »
It is not only "planned" as OP asked, it is an essential part of the generic syntax modes. Well written code can use both modes, I might add. For mode objectfpc, just the keyword specialize is added once, and the rest of the code is the exact same.
No typecasts...

The only "difficult" part is  the forward declaration of TmyClass to solve the interdependency that exists between TmyClass and TmyList. But that is specific to this example and can usually be avoided. To be more specific, since TmyList is specialized from TList<TMyclass>, and the implementation of TMyClass is later, this specific example needed a forward declaration of TMyClass, since TMyClass uses TmyList. But that is for the rest not of interest in the case of handling generics.
« Last Edit: February 19, 2024, 05:14:11 pm by Thaddy »
There is nothing wrong with being blunt. At a minimum it is also honest.

daniel_sap

  • Jr. Member
  • **
  • Posts: 64
Re: Generics typecast for parameterized type descendant
« Reply #5 on: February 20, 2024, 02:04:21 am »
Thank you all for the information, thoughts, guidelines

I didn't mention that i use delphi mode everywhere (except in cases where I do static library linking)

May be also the code example I gave is not explaining very good what happens on my side.
So, I did a search of TList<TObject>( in one of my projects and am attaching the screenshot here.

It returns 150 typecasts and this is only for the TList<TObject> cases, there are other also - TDoubleLinkedList<TObject>, TSingleLinkedList, TDoubleEndedQueue<> and more.
So, if I sum the typecasts in all the projects they are thousands.
Of course I can do the typecasts (even if I don't like it, how it looks) but wanted to ask, and rise it here, and learn something more

Thaddy

  • Hero Member
  • *****
  • Posts: 16409
  • Censorship about opinions does not belong here.
Re: Generics typecast for parameterized type descendant
« Reply #6 on: February 20, 2024, 06:45:02 am »

It returns 150 typecasts and this is only for the TList<TObject> cases, there are other also - TDoubleLinkedList<TObject>, TSingleLinkedList, TDoubleEndedQueue<> and more.
There should not be any typecasts in your code if you followed my advise.
Code: Pascal  [Select][+][-]
  1. {$mode delphi}
  2. type
  3.   TmyList = Tlist<TmyClass>;
  4.   TmyDoubleLinkedlist =TDoubleLinkedList<TmyClass>; // from which generics
  5.   TmySingleLinkedList = TSingleLinkedList<TMyClass>;  // framework did you
  6.   TmyDoubleEndedQueue  = TDoubleEndedQueue<TmyClass>; // get these.
If you work along those lines there are NO typecasts.
I like to see a bit more code, especially where you type cast and on what. All I can see beforehand is that you did not follow my advise and still specialize on variables instead of declaring a type. This is no different from well written C# or C++.

And I need to know which generics framework you use: FreePascal has several, all good but all slightly different to work with.
Note that if you write such a framework yourself, you might encounter some, but not many, typecasts in the framework internals if the generics you write are pointer based,
things like hardcasting to T, which would be bad design.

Plz give us some more code, I am a certified Borland/Inprise/Embarcadero CppBuilder and Delphi trainer (certified upto XE2). My example looks deceptively simple, but it is core to how to use generics, so look again: types, not vars, please and your need for typecasts will automagically disappear in most cases. Again, within the internals of a generics framework typecasts can happen, but usually not in the code that uses that framework.
Here's another small example from scratch:
Code: Pascal  [Select][+][-]
  1. program scratchlist;
  2. {$mode delphi}
  3. { Both examples compile but
  4.   the good example avoids repeated specialization}
  5. {$define good}
  6. type
  7.     TList<T> = class
  8.     private
  9.       FItems: array of T;
  10.     public
  11.       procedure Add(Value: T);
  12.     end;
  13. {$ifdef good}
  14.     // Good example use
  15.     TIntList = TList<integer>;
  16.     TStrList = TList<string>;
  17. {$endif}
  18.  
  19. procedure TList<T>.Add(Value: T);
  20. begin
  21.   SetLength(FItems, Length(FItems) + 1);
  22.   FItems[High(FItems)] := Value;
  23. end;
  24.  
  25. // Bad Example use:
  26. var
  27. {$ifndef good}
  28.   intList: TList<Integer>;
  29.   strList: TList<string>;
  30. {$else good}
  31.   intlist: TIntList;
  32.   strList: TstrList;
  33. {$endif}
  34. begin
  35. {$ifndef good}
  36.   intList := Tlist<integer>.create;
  37.   strList := Tlist<string>.Create;
  38. {$else good}
  39.   intList := TIntList.Create;
  40.   strList := TStrList.Create;
  41. {$endif}
  42.   intList.Add(100);
  43.   strList.Add('Hello, World!');
  44.   strList.Free;
  45.   intList.Free;
  46. end.
In mode objfpc the code for the bad example looks even more horrible.
Note the extra specialization you need when specializing var instead of type .
The code is just for demo purposes.
« Last Edit: February 20, 2024, 08:06:35 am by Thaddy »
There is nothing wrong with being blunt. At a minimum it is also honest.

TRon

  • Hero Member
  • *****
  • Posts: 3810
Re: Generics typecast for parameterized type descendant
« Reply #7 on: February 20, 2024, 07:11:41 am »
I'm with Thaddy on this one.

I never quite understood why almost all the Delphi examples I came across do not make use of type specialization but instead use the tedious long winded repetitive notation in their code.

Is there perhaps a downside that I am not aware of ?
I do not have to remember anything anymore thanks to total-recall.

440bx

  • Hero Member
  • *****
  • Posts: 4902
Re: Generics typecast for parameterized type descendant
« Reply #8 on: February 20, 2024, 07:16:29 am »
Is there perhaps a downside that I am not aware of ?
The following is a guess (easy to verify but I haven't).  When using type specialization wouldn't a breakpoint placed anywhere in the generic's code break for all specializations instead of a particular one ? (it might be desirable for the breakpoint to be "active" for only one type not all of them.)

(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

Thaddy

  • Hero Member
  • *****
  • Posts: 16409
  • Censorship about opinions does not belong here.
Re: Generics typecast for parameterized type descendant
« Reply #9 on: February 20, 2024, 07:58:47 am »
it might be desirable for the breakpoint to be "active" for only one type not all of them.
I see the point, but setting a breakpoint inside the generics code itself seems to make no difference between var or type specialization. (using my second example that I added).
There is nothing wrong with being blunt. At a minimum it is also honest.

daniel_sap

  • Jr. Member
  • **
  • Posts: 64
Re: Generics typecast for parameterized type descendant
« Reply #10 on: February 20, 2024, 09:40:53 am »
As all my life I specialized on variables, arguments ... it is a little bit difficult to switch to specialize type.
I'm thinking and reading stuff on the topic, so may be I could switch my mind to type specialization.

Another thing is if I have to create classes for every generic and specialization class combination. For example
Code: Pascal  [Select][+][-]
  1.   TMyClass = class
  2.  
  3.   end;
  4.  
  5.   TMyClassForList = TList<TMyClass>;
  6.   TMyClassForDoubleLinkedList = TList<TMyClass>;
  7.   TMyClassForSingle....
  8.  
I want to introduce one new class in the project and actually I have to introduce 5, 10 depending on what I would like to use it for.

Despite the extra classes introduced, I really don't like to specify what the class will be used for where it is declared. (I declare TMyClass but now this unit will use also the units where it will be used)
So, to avoid this I have to move this type specs in separate unit.

Another thing is that, for me it looks like the parameterized type is more like configuring the TList class.
TList class is doing the job, everything is implemented, but I want to give some more information how I want it to work.
No new methods or fields I want to introduce, no method to override also, so why I need a descendant class.

Also if I have hierarchy, I have to repeat the procedure
Code: Pascal  [Select][+][-]
  1.   TMyClass1 = class
  2.   end;
  3.   TMyClass1ForList = TList<TMyClass1>;
  4.   TMyClass1ForDoubleLinkedList = TList<TMyClass1>;
  5.   TMyClass1ForSingle....
  6.  
  7.   TMyClass2 = class(TMyClass1)
  8.   end;
  9.   TMyClass2ForList = TList<TMyClass2>;
  10.   TMyClass2ForDoubleLinkedList = TList<TMyClass2>;
  11.   TMyClass2ForSingle....
  12.  

Also I tried to specialize the type and it still not working (for variable assigning and argument passing)
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode delphi}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
  9.   Generics.Collections;
  10.  
  11. type
  12.  
  13.   TListWithObject = TList<TObject>;
  14.  
  15.   TMyClass = class
  16.   public
  17.     someText: String;
  18.   end;
  19.  
  20.   TListWithMyClass = TList<TMyClass>;
  21.  
  22.   { TForm1 }
  23.  
  24.   TForm1 = class(TForm)
  25.     Label1: TLabel;
  26.     procedure FormCreate(Sender: TObject);
  27.   private
  28.  
  29.   public
  30.  
  31.     procedure doit();
  32.     procedure doListWithObject(list: TListWithObject);
  33.  
  34.   end;
  35.  
  36. var
  37.   Form1: TForm1;
  38.  
  39. implementation
  40.  
  41. {$R *.lfm}
  42.  
  43. { TForm1 }
  44.  
  45. procedure TForm1.FormCreate(Sender: TObject);
  46. begin
  47.  
  48. end;
  49.  
  50. procedure TForm1.doit();
  51. var
  52.   myObjList: TListWithMyClass;
  53.   var2: TListWithObject;
  54. begin
  55.   myObjList := TListWithMyClass.Create();
  56.   var2 := myObjList;
  57.   doListWithObject(myObjList);
  58. end;
  59.  
  60. procedure TForm1.doListWithObject(list: TListWithObject);
  61. begin
  62.   Label1.Caption := IntToStr(list.Count);
  63. end;
  64.  
  65. end.
  66.  
  67.  
Have compilation error
Code: [Select]
unit1.pas(56,11) Error: Incompatible types: got "TList<Unit1.TMyClass>" expected "TList<System.TObject>"

Quote
Plz give us some more code, I am a certified Borland/Inprise/Embarcadero CppBuilder and Delphi trainer (certified upto XE2).
I can prepare some real code example, but the code above illustrates the issue.


Quote
And I need to know which generics framework you use: FreePascal has several, all good but all slightly different to work with.
Note that if you write such a framework yourself, you might encounter some, but not many, typecasts in the framework internals if the generics you write are pointer based,
things like hardcasting to T, which would be bad design.
I'm using Generics.Collections. But I thought that the typecast is not related to specific usage of generics. (may be I'm missing something)




VisualLab

  • Hero Member
  • *****
  • Posts: 614
Re: Generics typecast for parameterized type descendant
« Reply #11 on: February 20, 2024, 07:29:08 pm »
As all my life I specialized on variables, arguments ... it is a little bit difficult to switch to specialize type.
I'm thinking and reading stuff on the topic, so may be I could switch my mind to type specialization.

Another thing is if I have to create classes for every generic and specialization class combination. For example
Code: Pascal  [Select][+][-]
  1.   TMyClass = class
  2.  
  3.   end;
  4.  
  5.   TMyClassForList = TList<TMyClass>;
  6.   TMyClassForDoubleLinkedList = TList<TMyClass>;
  7.   TMyClassForSingle....
  8.  

If there are no differences in implementation between the TMyClassForList and TMyClassForDoubleLinkedList classes, this is an "artificial" creation of redundant classes and is completely unnecessary. There is no point in creating new types (classes) due to the role they are assigned in the program. Variables are enough for this. Descendants are created when there is a difference in their implementation, i.e. they have additional fields, methods, properties, events that differ among the descendants.

Also I tried to specialize the type and it still not working (for variable assigning and argument passing)
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode delphi}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
  9.   Generics.Collections;
  10.  
  11. type
  12.  
  13.   TListWithObject = TList<TObject>;
  14.  
  15.   TMyClass = class
  16.   public
  17.     someText: String;
  18.   end;
  19.  
  20.   TListWithMyClass = TList<TMyClass>;
  21.  
  22.   { TForm1 }
  23.  
  24.   TForm1 = class(TForm)
  25.     Label1: TLabel;
  26.     procedure FormCreate(Sender: TObject);
  27.   private
  28.  
  29.   public
  30.  
  31.     procedure doit();
  32.     procedure doListWithObject(list: TListWithObject);
  33.  
  34.   end;
  35.  
  36. var
  37.   Form1: TForm1;
  38.  
  39. implementation
  40.  
  41. {$R *.lfm}
  42.  
  43. { TForm1 }
  44.  
  45. procedure TForm1.FormCreate(Sender: TObject);
  46. begin
  47.  
  48. end;
  49.  
  50. procedure TForm1.doit();
  51. var
  52.   myObjList: TListWithMyClass; // <- this list stores objects of type TMyClass
  53.   var2: TListWithObject; // <- while this list stores objects of type TObject
  54. begin
  55.   myObjList := TListWithMyClass.Create(); // <- this list stores objects of type TMyClass
  56.   var2 := myObjList; // <- while this list stores objects of type TObject
  57.   doListWithObject(myObjList);
  58. end;
  59.  
  60. procedure TForm1.doListWithObject(list: TListWithObject);
  61. begin
  62.   Label1.Caption := IntToStr(list.Count);
  63. end;
  64.  
  65. end.
  66.  
  67.  
Have compilation error
Code: [Select]
unit1.pas(56,11) Error: Incompatible types: got "TList<Unit1.TMyClass>" expected "TList<System.TObject>"

You assign a variable of type TListWithMyClass to a variable of type TListWithObject. These are two different types.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5815
  • Compiler Developer
Re: Generics typecast for parameterized type descendant
« Reply #12 on: February 20, 2024, 09:15:03 pm »
Do you know if such feature is planned for implementation.
Does it look good for you to be implemented.
Do you see some issues in implementing this

No, something like this isn't planned, because it's hard to determine whether TList<TObject> and TList<TSomeSubClass> can be considered compatible, because unlike e.g. Java FPC does its generic handling more like C++ and thus you can have something like this:

Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2.  
  3. uses
  4.   Generics.Collections;
  5.  
  6. type
  7.   TSomeType = class
  8.     { no virtual here by choice! }
  9.     procedure Test;
  10.   end;
  11.  
  12.   TSomeOtherType = class(TSomeType)
  13.     procedure Test;
  14.   end;
  15.  
  16. procedure TSomeType.Test;
  17. begin
  18.   Writeln('TSomeType');
  19. end;
  20.  
  21. procedure TSomeOtherType.Test;
  22. begin
  23.   Writeln('TSomeOtherType');
  24. end;
  25.  
  26. procedure Test(aList: specialize TList<TSomeType>);
  27. var
  28.   t: TSomeType;
  29. begin
  30.   for t in aList do
  31.     t.Test;
  32. end;
  33.  
  34. procedure Test(aList: specialize TList<TSomeOtherType>);
  35. var
  36.   t: TSomeOtherType;
  37. begin
  38.   for t in aList do
  39.     t.Test;
  40. end;
  41.  
  42. var
  43.   l1: specialize TList<TSomeType>;
  44.   l2: specialize TList<TSomeOtherType>;
  45. begin
  46.   l1 := specialize TList<TSomeType>.Create;
  47.   l1.Add(TSomeType.Create);
  48.  
  49.   l2 := specialize TList<TSomeOtherType>.Create;
  50.   l2.Add(TSomeOtherType.Create);
  51.  
  52.   // will call TSomeType.Test
  53.   Test(l1);
  54.  
  55.   // will call TSomeOtherType.Test
  56.   Test(l2);
  57.  
  58.   // will call TSomeType.Test which could be totally wrong
  59.   Test(specialize TList<TSomeType>(l2));
  60. end.

 

TinyPortal © 2005-2018