Generics proposals

From Lazarus wiki
Jump to navigationJump to search

Why

  • Typesafety !
  • Speed
  • Readability

See "Already existing stuff" for possible but lacking ways right now.

Usage example

(dannym proposal)

 type
 TGenericList<T> = class
 public
   procedure Add(stuff: T);
 end;

<plugwash> tmycollection=template(tobject)[x] <plugwash> tintegercollection=tmycollection[integer] <oliebol> template mycollection (x: tobject) is more logical

Terms

type user:

  • the function in the compiler that defines a variable or return type and thus 'uses a (already defined) type'.

immediate type:

  • not generic and not specialized, i.e. 'normal type'

generic type:

  • template for a class with some unspecified types, never to be filled in into this class type. Only by specialisation.

specialization:

  • copy generic type class to new specialized type class

type parameters:

  • T and Q, the unknown types for the generic TSomeGeneric<T,Q>

specialized type:

  • a generic type with all type parameters know, written into a real class type (tree) and reused if possible

Changes

Changes in class/record definition representation

Each class definition representation has added fields for:

 - class instantiation mode
    0  immediate type
    1  generic type, no instantiation, just generate specialized type
    2  specialized type
 - when mode 2:
     generic type uplink (what, unitname, typename?)
   when mode 1:
     list of specialized types known so far (and where)
   when mode 0: 
     nothing

Changes in 'type user' compilation (for class/record types only)

Each class type user will have to check mode of the class type.

If mode is immediate

 - proceed as always till now.

If mode is specialized

 - proceed as always till now 
 - keep in mind the generic type for some checks later (and debugging).

If mode is generic, this:

 - check 'list of specialized types' for the type parameter to use.
   If there is already a specialized type, use that.
   (given that it is compatible with 'compile parameters' XXXX)
 - If not available, clone the generic type in the tree, and fill in the
   type params with the actual types to use.
   Write that out to XXXX ppu file.
   Remember the new specialized type for later in the list by the 
   generic type and type params used.

type params

Generics store a list of the type params (names).

 ('T', 'Q')

And Specializations store a list of type mappings from the real types to the type params, in the way of

 type
   T = Integer;
   Q = Boolean;

of course these are local to this class/record specialization.

Other things to note (for *later*)

What if a method implementation depends on the type used to know how the implementation should look like ?

 procedure TGenericList<T is TObject>.Add(value: T);
 procedure TGenericList<T is Integer>.Add(value: T);
 ?

or more cryptic like

 procedure TGenericList<TObject>.Add(value: T);
 procedure TGenericList<Integer>.Add(value: T);
 ?

How to do internal storage classes

 type
 TGenericList<T> = class
 private
   PNode<T> = ^TNode<T>;
   TNode<T> = record
     prev,next: PNode<T>;
     data: T;
   end;
   head, tail: PNode;
   cnt: Integer;
 public
   procedure Add(stuff: T);
 end;
 ?

This implies that generics are best implemented both for records and for classes alike! Also implies that pointer types need to support generic too.

Then it becomes easy again:

 type
 PNode<T> = ^TNode<T>;
 TNode<T> = record
   prev,next: PNode<T>;
   data: T;
 end;
 type
 TGenericList<T> = class
 private
   head, tail: PNode<T>;
   cnt: Integer;
 public
   procedure Add(stuff: T);
 end;

This again implies that one specialization should chain other specializations (probably also when deriving from a generic).

How to derive from the generic type

It would be good if generic types could be derived from.

 type
 TGenericEventyList<T> = class(TGenericList<T>)
 public
   procedure Add(value: T); override;
 end;
 ...

Implementation details

Sanity checks

  • Generic types cannot be instantiated from
  • (Generic types can be derived from)
  • Specialized types cannot be derived from
  • Normal types cannot use generic types in its definition
  • Normal types can use specialized types in its definition
  • There are no half specialized types (one type param specified, the other not)
  • prevent loops while 'unpacking' specializations (loop counter field in every specialization)

Things to note and not forget

  • A class or record generic can use itself as type within its definition

Example:

 type
 TBla<T> = class
   parent: TBla<T>;
 end;
  • A class or record generic can use another generic as type within its definition
 type
 TBla<T> = class
   parent: TBlo<T>;
 end;

Already existing stuff

metaclasses

 type
 TObjectClass = class of TObject;
 TTypedList = class
 private
   Fwanttype: TObjectClass;
   ...
 public
   constructor Create(itemtype: TObjectClass);
   procedure Add(item: TObject);
   ...
 end;
 constructor TTypedList.Create(itemtype: TObjectClass);
 begin
   Fwanttype := itemtype;
   ...
 end;
 procedure TTypedList.Add(item: TObject);
 begin
   assert(item.Class = Fwanttype);
   ...
 end;

Advantages

  • already works

Disadvantages

  • slow at runtime
  • cannot do simple types