Sign in Join

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

Mailing lists / fpc-pascal / 2012-12

Feature announcement: Generic type constraints

4 messages · 2 people · –

Reply by email Subscribe to this list Your subscription

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

Sven Barth

Hello Free Pascal community!

I'm pleased to announce the addition of generic type constraints to Free Pascal (beginning from revision 23158).

Overview:

Generic type constraints allow to restrict the set of possible types that can be used to specialize a given generic. So for example one can restrict a type parameter to only allow descendants of a given class type that implement a specific interface.

Syntax:

A type parameter declaration now looks like this: a set of comma seperated generic parameter names is either followed by a ";" or a ":". If the former than another list of generic type parameters follows. Otherwise after the ":" follows a comma seperated list of the following constraints:

- "record"
- "class"
- a class identifer
- an interface identifier
[- "constructor" (only allowed in mode Delphi; for the reason see "Delphi compatibility")]

After such a constraint list follows either a ">" which means that this is the last parameter list or a ";" after which another list of generic type parameters follows.

Semantics:

The rules for the above mentioned constraints are as follows:

- "record" can only be used by itself and is mutually exclusive to each other constraint; only record types can be used to satisfy this constraint
- "class" can be used together with any other constraint except "record" and a class identifier; it defines that the specialization type is a class type that's either "TObject" or a descendant of it
- multiple interface identifiers can be used to denote interfaces that the given identifier must implement; they are mutually exclusive to "record"; giving multiple interfaces implicitly requires the specialization parameter to be a class type; if only a single interface is given the specialization type can be an interface derived from that interface as well
- one class identifier can be given (mutually exclusive to "class" and "record") to denote that the specialization parameter must be either of the given class type or a descendant of it
[- "constructor" is mutually exclusive to "record" and behaves like "class" ]

The order of the constraints is not important.

Examples:

type
   { only class types deriving from TObject can be used }
   generic TExample1<T: class> = class
   end;

{ the same as the above with the exception that this is not valid code in Delphi }
generic TExample2<T: TObject> = class

   end;

{ the parameter must derive from a "TStrings" class; valid types are for example "TStrings" or "TStringList" }
generic TExample3<T: TStrings> = class

   end;

{ valid types are for example "IInterface", "IUnknown", "TInterfacedObject" or basically anything that derives from "TComponent" as that class implemnts "IInterface" as well }
generic TExample4<T: IInterface> = class

   end;

{ unlike above interface types ("IInterface", "IUnknown", etc.) are not valid here }
generic TExample5<T: class, IInterface> = class

   end;
   { same as above; the order is not important }
   generic TExample6<T: IInterface, class> = class
   end;

{ a class that implements the two given interfaces though this does not need to be the case on the same level; e.g. a class that derives from "TInterfacedObject" that implements "ISomeOtherInterface" is valid as well }
generic TExample7<T: IInterface, ISomeOtherInterface> = class

   end;
   { the same as above; the "class" is redundant }
   generic TExample8<T: class, IInterface, ISomeOtherInterface> = class
   end;
   { the type must be a record }
   generic TExample9<T: record> = class
   end;
   { "T1" must be a descendant of "TObject" while "T2" can be of any type }
   generic TExample10<T1: class; T2> = class
   end;

{ the inverse of the above: "T1" can be any type while "T2" needs to be a descendant of "TObject" }
generic TExample11<T1; T2: class> = class

   end;

{ "T1" and "T2" need to be a descendant of "TStrings", "T3" can be of any type and "T4" needs to be either a descendant of "IInterface" or a descendant of a class that implements "IInterface" } generic TExample12<T1, T2: TStrings; T3; T4: IInterface> = class

   end;

Delphi compatibility:

The compiler can correctly handle Delphi compatible type constraints in mode Delphi. It does not check though that the constraint ": TObject" is not used which is not allowed in Delphi.

Also in mode Delphi the constraint "constructor" is available as well. This constraint is not available in other modes because it is handled rather strangely in Delphi and in concept behaves like a complicated "class" constraint and is very likely a relict of Delphi.Net.

Delphi's behavior is as follows:
If the "constructor" constraint is not given (but e.g. "class" or class identifier is) one can NOT call ".Create" on that type (even if the constructor would need parameters). What is allowed however is to call a constructor that is not named "Create", e.g. "constructor CreateFromGeneric".

It would be understandable if the "constructor" constraint would require the programmer to have a parameterless constructor "Create" defined in the given class type, but the compiler does not care where in the class hierarchy of the type the constructor is implemented and thus "constructor" behaves the same as "class" with the exception of the check for ".Create" which is not implemented in Free Pascal.

Future work:

Adjust generics provided by FPC so that e.g. "fgl.TFPGObjectList" requires the type to be a "class".

Currently only the specialization itself is influenced by the constraints as the parsing of a generic is - compared to Delphi - very lax. In future commits this will be changed and for example the calling of methods (like ".Free" or ".Create") will be forbidden if no such method is available for the type (e.g. through a class/record/type helper declared inside the generic).

This of course might lead to the necessity to extend the capabilites of constraints. There are currently no extensions planned, but for example one could think of the following additions which would allow specific operations on the types (e.g. usage of "Low"):

- "enum": the type must be a enumerationn type
- "ordinal": the type must be one of the ordinal types (Integer, LongWord, etc.)
- "array": the type must be an array type
- "string": the type must be one of the string types
- "object": the type must be an "object" type (analogous to "record")
- object identifier: the type must derive from the given object type
- support for Objective Pascal types like objcclass, objcprotcol, etc.
- "operator X": the type must support the operator "X" (available for all other constraints)

Conclusion:

I hope generic constraints will be a viable addition to the Free Pascal language.

Currently no changes by users are required, but as mentioned in "Future work" this is likely to change and thus I'd like to invite you to check your own generics whether they'd fullfill stricter rules and if not add constraints or request the addition of new constraints (together with valid usecases).

Also I'd invite you to test type constraints thoroughly so that errors can be eliminated before the next major release of FPC.

Regards,
Sven

Reply by email

Sven Barth
to leledumbo
leledumbo wrote
Wonderful, Sven! Now we could have an even better generics feature than Delphi or C++ (forget Java and .NET)

Ehm... you know that AFAIK all those languages have constraints as well? :)

Though if your comment is regarding the possible future developments: of course :D

Regards,
Sven

Reply by email