Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / lazarus / 2012-05

Generics type as parameter

10 messages · 3 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Leonardo M. Ramé

I'm trying to work in an abstract way with generics, I need to create a method receiving a un-specialized generic and do some work on it.

Example:

function ProcessGeneric(AList: TFPGList);
begin
  ... do something ...
end;

Imagine your application uses many lists of specialized objects, such as TCustomer, TBooks, TColors. To create a list of those types, you'd do something like this:

  TCustomers = specialize TFPGList<TCustomer>;
  TBooks = specialize TFPGList<TBook>;
  TColors = specialize TFPGList<TColor>;

Then, you need to pass any of those types to "ProcessGeneric":

var
  lCustomers: TCustomers;
  lBooks: TBooks;
  lColors: TColors;
begin
  ProcessGeneric(lCustomers);
  ProcessGeneric(lBooks);
  ProcessGeneric(lColors);
end;

Without generics, I can use, for example, a TObjectList, or a TCollection, without any problem. Is there a way to do this using Generics?.

Regards,

Reply by email

Michalis Kamburelis
to Leonardo M. Ramé
Leonardo M. Ram? wrote
Without generics, I can use, for example, a TObjectList, or a TCollection, without any problem. Is there a way to do this using Generics?.

All generics in FGL unit descend from non-generic TFPSList class. It can be used if you need to treat them in a uniform manner.

Michalis

Reply by email

Sven Barth
to Leonardo M. Ramé: I'm trying to work in an abstract way with generics, I need to create a method receiving…
Leonardo M. Ram? wrote 34 lines
I'm trying to work in an abstract way with generics, I need to create a method receiving a un-specialized generic and do some work on it. Example: function ProcessGeneric(AList: TFPGList); begin ... do something ... end; Imagine your application uses many lists of specialized objects, such as TCustomer, TBooks, TColors. To create a list of those types, you'd do something like this: TCustomers = specialize TFPGList<TCustomer>; TBooks = specialize TFPGList<TBook>; TColors = specialize TFPGList<TColor>; Then, you need to pass any of those types to "ProcessGeneric": var lCustomers: TCustomers; lBooks: TBooks; lColors: TColors; begin ProcessGeneric(lCustomers); ProcessGeneric(lBooks); ProcessGeneric(lColors); end; Without generics, I can use, for example, a TObjectList, or a TCollection, without any problem. Is there a way to do this using Generics?.

There are basically to ways you can achive this (one is supported by FPC the other not yet):
* descend the generic from a baseclass you can work with (e.g. as Michalis already mentioned the FGL classes all descend from a baseclass that is tailored to the needs of the generic, e.g. TFPGList descends from TFPSList)
* use generic procedures (this is not yet supported with FPC, it's on my ToDo list though).

In case of generic procedures your example will then look like this (in mode Delphi):

=== example begin ===

procedure ProcessGeneric<T>(AList: TFPGList<T>);
begin
   ...
end;
var
    lCustomers: TCustomers;
    lBooks: TBooks;
    lColors: TColors;
begin
    ProcessGeneric<TCustomer>(lCustomers);
    ProcessGeneric<TBook>(lBooks);
    ProcessGeneric<TColor>(lColors);
end;

=== example end ===

(in mode ObjFPC there would be a bit more "generic" and "specialize" of course)

In mode Delphi you can also use a workaround in the current version (2.7.1) of FPC (it should work at least, but I haven't tested it...):

=== workaround begin ===

TMyListProcessor<T> = class
   class procedure ProcessGeneric(AList: TFPGList<T>);
end;
class procedure TMyListProcessor<T>(AList: TFPGList<T>);
begin
end;
var
    lCustomers: TCustomers;
    lBooks: TBooks;
    lColors: TColors;
begin
    TMyListProcessor<TCustomer>.ProcessGeneric(lCustomers);
    TMyListProcessor<TBook>.ProcessGeneric(lBooks);
    TMyListProcessor<TColor>.ProcessGeneric(lColors);
end;

=== workaround end ===

This won't work as written above in mode ObjFPC (yet), because you'd always need to specialize the class in a type section.

Regards,
Sven

Reply by email

Leonardo M. Ramé
to Sven Barth
Sven Barth wrote 103 lines
On 12.05.2012 13:32, Leonardo M. Ram? wrote: >I'm trying to work in an abstract way with generics, I need to create a >method receiving a un-specialized generic and do some work on it. > >Example: > >function ProcessGeneric(AList: TFPGList); >begin > ... do something ... >end; > >Imagine your application uses many lists of specialized objects, such as >TCustomer, TBooks, TColors. To create a list of those types, you'd do >something like this: > > TCustomers = specialize TFPGList<TCustomer>; > TBooks = specialize TFPGList<TBook>; > TColors = specialize TFPGList<TColor>; > >Then, you need to pass any of those types to "ProcessGeneric": > >var > lCustomers: TCustomers; > lBooks: TBooks; > lColors: TColors; > >begin > ProcessGeneric(lCustomers); > ProcessGeneric(lBooks); > ProcessGeneric(lColors); >end; > >Without generics, I can use, for example, a TObjectList, or a >TCollection, without any problem. Is there a way to do this using >Generics?. There are basically to ways you can achive this (one is supported by FPC the other not yet): * descend the generic from a baseclass you can work with (e.g. as Michalis already mentioned the FGL classes all descend from a baseclass that is tailored to the needs of the generic, e.g. TFPGList descends from TFPSList) * use generic procedures (this is not yet supported with FPC, it's on my ToDo list though). In case of generic procedures your example will then look like this (in mode Delphi): === example begin === procedure ProcessGeneric<T>(AList: TFPGList<T>); begin ... end; var lCustomers: TCustomers; lBooks: TBooks; lColors: TColors; begin ProcessGeneric<TCustomer>(lCustomers); ProcessGeneric<TBook>(lBooks); ProcessGeneric<TColor>(lColors); end; === example end === (in mode ObjFPC there would be a bit more "generic" and "specialize" of course) In mode Delphi you can also use a workaround in the current version (2.7.1) of FPC (it should work at least, but I haven't tested it...): === workaround begin === TMyListProcessor<T> = class class procedure ProcessGeneric(AList: TFPGList<T>); end; class procedure TMyListProcessor<T>(AList: TFPGList<T>); begin end; var lCustomers: TCustomers; lBooks: TBooks; lColors: TColors; begin TMyListProcessor<TCustomer>.ProcessGeneric(lCustomers); TMyListProcessor<TBook>.ProcessGeneric(lBooks); TMyListProcessor<TColor>.ProcessGeneric(lColors); end; === workaround end === This won't work as written above in mode ObjFPC (yet), because you'd always need to specialize the class in a type section. Regards, Sven

Thanks Sven and Michalis. Using the suggestion by Michalis, I got a solution mixing Generics and regular objects.

What I'm pursuing is to replace my TCollection based ORM, with a Generics based one, this allow users to write less code, in a clearer way.

The way the ORM works, is to create a TCollection instance, then execute an ORM's method to load/save data into/from it, example:

var
  lCustomers: TCustomers; // this is a TCollection descendant
begin
  lCustomers := TCustomers.Create;
  FConnector.LoadData(lCustomers, []);
  ... do something with lCustomers ...
  lCustomers.Free;
end;

As you can see, FConnector.LoadData receives a TCollection as parameter, and using RTTI, it fills each TCustomer published property.

With Michalis's solution, I can turn my ORM from TCollections to Generics with very little changes, see how small is the unit customer now, instead of a complete TCollection/TCollectionItem definition:

///// ---- customer.pas ----
unit customer;
{$mode objfpc}
interface
uses
  Classes;
type
  TCustomer = class
  private
    FName: string;
    FAge: Integer;
  published
    property Name: string read FName write FName;
    property Age: Integer read FAge write FAge;
  end;
implementation
end.
///// ---- generics.pas ----
program generics;
{$mode objfpc}

uses
sysutils,
customer,
typinfo,
variants,

  fgl;
type
  TCustomerList = specialize TFPGList<TCustomer>;
procedure ProcessGeneric(AList: TFPSList);
var
  I: Integer;
  lVal: string;
begin
  for I:= 0 to AList.Count - 1 do
  begin
    lVal := string(GetPropValue(TObject(AList.Items[I]^), 'Name'));
    writeln(Format('%s (%d)', [lVal, I]));
  end;
end;
var
  lList: TCustomerList;
  lCustomer: TCustomer;
begin
  lList := TCustomerList.Create;
  lCustomer := TCustomer.Create;
  lCustomer.Name := 'Mart?n';
  lCustomer.Age := 37;
  lList.Add(lCustomer);
  lCustomer := TCustomer.Create;
  lCustomer.Name := 'Nico';
  lCustomer.Age := 28;
  lList.Add(lCustomer);
  ProcessGeneric(lList);
  lList.Free;
end.

Reply by email

Leonardo M. Ramé
to Leonardo M. Ramé
Leonardo M. Ram? wrote 26 lines
Thanks Sven and Michalis. Using the suggestion by Michalis, I got a solution mixing Generics and regular objects. What I'm pursuing is to replace my TCollection based ORM, with a Generics based one, this allow users to write less code, in a clearer way. The way the ORM works, is to create a TCollection instance, then execute an ORM's method to load/save data into/from it, example: var lCustomers: TCustomers; // this is a TCollection descendant begin lCustomers := TCustomers.Create; FConnector.LoadData(lCustomers, []); ... do something with lCustomers ... lCustomers.Free; end; As you can see, FConnector.LoadData receives a TCollection as parameter, and using RTTI, it fills each TCustomer published property. With Michalis's solution, I can turn my ORM from TCollections to Generics with very little changes, see how small is the unit customer now, instead of a complete TCollection/TCollectionItem definition:

Following the same subject, inside a method receiving an TFPSList, how can I know the type of items it will contain, even if the list is empty?.

In a TCollection, I can use myCollection.ItemClass to know it, but in a specialized Generic type, how can I know, in an abstract way the type of a TFPGList<T>?. I mean, what type is T?.

Regards,

Reply by email

Sven Barth
to Leonardo M. Ramé
Leonardo M. Ram? wrote 35 lines
On 2012-05-12 13:07:18 -0300, Leonardo M. Ram? wrote: > Thanks Sven and Michalis. Using the suggestion by Michalis, I got a > solution mixing Generics and regular objects. > > What I'm pursuing is to replace my TCollection based ORM, with a > Generics based one, this allow users to write less code, in a clearer > way. > > The way the ORM works, is to create a TCollection instance, then execute > an ORM's method to load/save data into/from it, example: > > var > lCustomers: TCustomers; // this is a TCollection descendant > begin > lCustomers := TCustomers.Create; > FConnector.LoadData(lCustomers, []); > ... do something with lCustomers ... > lCustomers.Free; > end; > > As you can see, FConnector.LoadData receives a TCollection as parameter, > and using RTTI, it fills each TCustomer published property. > > With Michalis's solution, I can turn my ORM from TCollections to > Generics with very little changes, see how small is the unit customer > now, instead of a complete TCollection/TCollectionItem definition: > Following the same subject, inside a method receiving an TFPSList, how can I know the type of items it will contain, even if the list is empty?. In a TCollection, I can use myCollection.ItemClass to know it, but in a specialized Generic type, how can I know, in an abstract way the type of a TFPGList<T>?. I mean, what type is T?.

You can't. That's why the method with the "parent class" might be considered less powerful than the one with generic methods/procedures.

Regards,
Sven

Reply by email

Leonardo M. Ramé
to Sven Barth
Sven Barth wrote 43 lines
On 13.05.2012 02:49, Leonardo M. Ram? wrote: >On 2012-05-12 13:07:18 -0300, Leonardo M. Ram? wrote: >>Thanks Sven and Michalis. Using the suggestion by Michalis, I got a >>solution mixing Generics and regular objects. >> >>What I'm pursuing is to replace my TCollection based ORM, with a >>Generics based one, this allow users to write less code, in a clearer >>way. >> >>The way the ORM works, is to create a TCollection instance, then execute >>an ORM's method to load/save data into/from it, example: >> >>var >> lCustomers: TCustomers; // this is a TCollection descendant >>begin >> lCustomers := TCustomers.Create; >> FConnector.LoadData(lCustomers, []); >> ... do something with lCustomers ... >> lCustomers.Free; >>end; >> >>As you can see, FConnector.LoadData receives a TCollection as parameter, >>and using RTTI, it fills each TCustomer published property. >> >>With Michalis's solution, I can turn my ORM from TCollections to >>Generics with very little changes, see how small is the unit customer >>now, instead of a complete TCollection/TCollectionItem definition: >> > >Following the same subject, inside a method receiving an TFPSList, how >can I know the type of items it will contain, even if the list is >empty?. > >In a TCollection, I can use myCollection.ItemClass to know it, but in a >specialized Generic type, how can I know, in an abstract way the type of >a TFPGList<T>?. I mean, what type is T?. You can't. That's why the method with the "parent class" might be considered less powerful than the one with generic methods/procedures. Regards, Sven

Hm. Another thing I'm stuck on, is specialized classes doesn't seem to have a ClassName, am I righ?.

Reply by email

Sven Barth
to Leonardo M. Ramé
Leonardo M. Ram? wrote 48 lines
On 2012-05-13 10:53:42 +0200, Sven Barth wrote: > On 13.05.2012 02:49, Leonardo M. Ram? wrote: >> On 2012-05-12 13:07:18 -0300, Leonardo M. Ram? wrote: >>> Thanks Sven and Michalis. Using the suggestion by Michalis, I got a >>> solution mixing Generics and regular objects. >>> >>> What I'm pursuing is to replace my TCollection based ORM, with a >>> Generics based one, this allow users to write less code, in a clearer >>> way. >>> >>> The way the ORM works, is to create a TCollection instance, then execute >>> an ORM's method to load/save data into/from it, example: >>> >>> var >>> lCustomers: TCustomers; // this is a TCollection descendant >>> begin >>> lCustomers := TCustomers.Create; >>> FConnector.LoadData(lCustomers, []); >>> ... do something with lCustomers ... >>> lCustomers.Free; >>> end; >>> >>> As you can see, FConnector.LoadData receives a TCollection as parameter, >>> and using RTTI, it fills each TCustomer published property. >>> >>> With Michalis's solution, I can turn my ORM from TCollections to >>> Generics with very little changes, see how small is the unit customer >>> now, instead of a complete TCollection/TCollectionItem definition: >>> >> >> Following the same subject, inside a method receiving an TFPSList, how >> can I know the type of items it will contain, even if the list is >> empty?. >> >> In a TCollection, I can use myCollection.ItemClass to know it, but in a >> specialized Generic type, how can I know, in an abstract way the type of >> a TFPGList<T>?. I mean, what type is T?. > > You can't. That's why the method with the "parent class" might be considered > less powerful than the one with generic methods/procedures. > > Regards, > Sven > Hm. Another thing I'm stuck on, is specialized classes doesn't seem to have a ClassName, am I righ?.

Specialized classes are just normal classes. They DO have a class name, but it is not the one that you might expect.

Following example:

=== example begin ===

type
   TTest<T> = class
   end;
   TTest1 = specialize TTest<LongInt>;
   TTest2 = specialize TTest<LongInt>;
begin
   with TTest1.Create do
     try
       Writeln(ClassName);
     finally
       Free;
     end;
   with TTest2.Create do
     try
       Writeln(ClassName);
     finally
       Free;
     end;
end.

=== example end ===

In both cases the same name "TTest$1$crc31B95292" will be written which is composed out of the name of the generic (which is "TTest$1", where the "$1" stands for "has one generic parameter") and the crc of the given type parameters. This name is an implementation detail though, so DO NOT RELY ON IT, because I WILL change it sooner or later (to be more like "TTest<System.LongInt>").

The fact that the name is in both cases the same is because the generic is only specialized once per unit, so that code like the following can find the correct specialization (this works in mode Delphi):

=== example begin ===

var
   t: TTest<LongInt>;
begin
   t := TTest<LongInt>.Create;
   try
     t.Value := TTest<LongInt>.SomeConstant;
   finally
     t.Free;
   end;
end.

=== example end ===

Regards,
Sven

Reply by email

Leonardo M. Ramé
to Sven Barth
Sven Barth wrote 111 lines
On 13.05.2012 12:46, Leonardo M. Ram? wrote: >On 2012-05-13 10:53:42 +0200, Sven Barth wrote: >>On 13.05.2012 02:49, Leonardo M. Ram? wrote: >>>On 2012-05-12 13:07:18 -0300, Leonardo M. Ram? wrote: >>>>Thanks Sven and Michalis. Using the suggestion by Michalis, I got a >>>>solution mixing Generics and regular objects. >>>> >>>>What I'm pursuing is to replace my TCollection based ORM, with a >>>>Generics based one, this allow users to write less code, in a clearer >>>>way. >>>> >>>>The way the ORM works, is to create a TCollection instance, then execute >>>>an ORM's method to load/save data into/from it, example: >>>> >>>>var >>>> lCustomers: TCustomers; // this is a TCollection descendant >>>>begin >>>> lCustomers := TCustomers.Create; >>>> FConnector.LoadData(lCustomers, []); >>>> ... do something with lCustomers ... >>>> lCustomers.Free; >>>>end; >>>> >>>>As you can see, FConnector.LoadData receives a TCollection as parameter, >>>>and using RTTI, it fills each TCustomer published property. >>>> >>>>With Michalis's solution, I can turn my ORM from TCollections to >>>>Generics with very little changes, see how small is the unit customer >>>>now, instead of a complete TCollection/TCollectionItem definition: >>>> >>> >>>Following the same subject, inside a method receiving an TFPSList, how >>>can I know the type of items it will contain, even if the list is >>>empty?. >>> >>>In a TCollection, I can use myCollection.ItemClass to know it, but in a >>>specialized Generic type, how can I know, in an abstract way the type of >>>a TFPGList<T>?. I mean, what type is T?. >> >>You can't. That's why the method with the "parent class" might be considered >>less powerful than the one with generic methods/procedures. >> >>Regards, >>Sven >> > >Hm. Another thing I'm stuck on, is specialized classes doesn't seem to >have a ClassName, am I righ?. > Specialized classes are just normal classes. They DO have a class name, but it is not the one that you might expect. Following example: === example begin === type TTest<T> = class end; TTest1 = specialize TTest<LongInt>; TTest2 = specialize TTest<LongInt>; begin with TTest1.Create do try Writeln(ClassName); finally Free; end; with TTest2.Create do try Writeln(ClassName); finally Free; end; end. === example end === In both cases the same name "TTest$1$crc31B95292" will be written which is composed out of the name of the generic (which is "TTest$1", where the "$1" stands for "has one generic parameter") and the crc of the given type parameters. This name is an implementation detail though, so DO NOT RELY ON IT, because I WILL change it sooner or later (to be more like "TTest<System.LongInt>"). The fact that the name is in both cases the same is because the generic is only specialized once per unit, so that code like the following can find the correct specialization (this works in mode Delphi): === example begin === var t: TTest<LongInt>; begin t := TTest<LongInt>.Create; try t.Value := TTest<LongInt>.SomeConstant; finally t.Free; end; end. === example end === Regards, Sven

Ok, so, what happens if I declare the same specialization in different units, then pass instances of each specialization to the same method, its ClassName should be the same, no?.

for example:

----
unit1;
...
type
  TCustomers = specialize TFPGList<TCustomer>;
-----
----
unit2;
...
type
  TCustomers_from_branch1 = specialize TFPGList<TCustomer>;
-----

What I would like, is, when I ask for the instance's classname, it write TCustomers and TCustomers_from_branch1, instead of TFPGList$1$....

Is this planned?.

Reply by email

Sven Barth
to Leonardo M. Ramé
Leonardo M. Ram? wrote 116 lines
On 2012-05-13 13:38:36 +0200, Sven Barth wrote: > On 13.05.2012 12:46, Leonardo M. Ram? wrote: >> On 2012-05-13 10:53:42 +0200, Sven Barth wrote: >>> On 13.05.2012 02:49, Leonardo M. Ram? wrote: >>>> On 2012-05-12 13:07:18 -0300, Leonardo M. Ram? wrote: >>>>> Thanks Sven and Michalis. Using the suggestion by Michalis, I got a >>>>> solution mixing Generics and regular objects. >>>>> >>>>> What I'm pursuing is to replace my TCollection based ORM, with a >>>>> Generics based one, this allow users to write less code, in a clearer >>>>> way. >>>>> >>>>> The way the ORM works, is to create a TCollection instance, then execute >>>>> an ORM's method to load/save data into/from it, example: >>>>> >>>>> var >>>>> lCustomers: TCustomers; // this is a TCollection descendant >>>>> begin >>>>> lCustomers := TCustomers.Create; >>>>> FConnector.LoadData(lCustomers, []); >>>>> ... do something with lCustomers ... >>>>> lCustomers.Free; >>>>> end; >>>>> >>>>> As you can see, FConnector.LoadData receives a TCollection as parameter, >>>>> and using RTTI, it fills each TCustomer published property. >>>>> >>>>> With Michalis's solution, I can turn my ORM from TCollections to >>>>> Generics with very little changes, see how small is the unit customer >>>>> now, instead of a complete TCollection/TCollectionItem definition: >>>>> >>>> >>>> Following the same subject, inside a method receiving an TFPSList, how >>>> can I know the type of items it will contain, even if the list is >>>> empty?. >>>> >>>> In a TCollection, I can use myCollection.ItemClass to know it, but in a >>>> specialized Generic type, how can I know, in an abstract way the type of >>>> a TFPGList<T>?. I mean, what type is T?. >>> >>> You can't. That's why the method with the "parent class" might be considered >>> less powerful than the one with generic methods/procedures. >>> >>> Regards, >>> Sven >>> >> >> Hm. Another thing I'm stuck on, is specialized classes doesn't seem to >> have a ClassName, am I righ?. >> > > Specialized classes are just normal classes. They DO have a class name, but > it is not the one that you might expect. > > Following example: > > === example begin === > > type > TTest<T> = class > > end; > > TTest1 = specialize TTest<LongInt>; > TTest2 = specialize TTest<LongInt>; > > begin > with TTest1.Create do > try > Writeln(ClassName); > finally > Free; > end; > with TTest2.Create do > try > Writeln(ClassName); > finally > Free; > end; > end. > > === example end === > > In both cases the same name "TTest$1$crc31B95292" will be written which is > composed out of the name of the generic (which is "TTest$1", where the "$1" > stands for "has one generic parameter") and the crc of the given type > parameters. This name is an implementation detail though, so DO NOT RELY ON > IT, because I WILL change it sooner or later (to be more like > "TTest<System.LongInt>"). > > The fact that the name is in both cases the same is because the generic is > only specialized once per unit, so that code like the following can find the > correct specialization (this works in mode Delphi): > > === example begin === > > var > t: TTest<LongInt>; > begin > t := TTest<LongInt>.Create; > try > t.Value := TTest<LongInt>.SomeConstant; > finally > t.Free; > end; > end. > > === example end === > > Regards, > Sven > Ok, so, what happens if I declare the same specialization in different units, then pass instances of each specialization to the same method, its ClassName should be the same, no?.

Yes, though currently a TFPGList<LongInt> declared in different units won't be compatible to each other (it's planned to change this).

Show quoted text 21 lines
for example: ---- unit1; ... type TCustomers = specialize TFPGList<TCustomer>; ----- ---- unit2; ... type TCustomers_from_branch1 = specialize TFPGList<TCustomer>; ----- What I would like, is, when I ask for the instance's classname, it write TCustomers and TCustomers_from_branch1, instead of TFPGList$1$.... Is this planned?.

No, this won't be possible. As I said the class name that is used is based on the "specialize ..." part. The type name on the left is only an alias to that specialization type.

Regards,
Sven

Reply by email