Sign in Join

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

Mailing lists / fpc-pascal / 2015-11

Feature announcement: Generic functions, procedures and methods

21 messages · 8 people · –

Reply by email Subscribe to this list Your subscription

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

Sven Barth

Hello together!

I'm pleased to finally announce the addition of generic functions, procedures and methods (collectively called "routines") to Free Pascal which allows writing type safe methods that can be used for multiple types.

Syntax:

The syntax for declaring a generic routine is similar to declaring a normal routine and also includes support for class methods:

generic [class] (procedure|function)
IDENTIFIER<TYPEARGLIST>[(PARAMETERLIST)][: RESULTTYPE]; MODIFIERS;

For the TYPEARGLIST the same rules apply as for generic types. Type parameters declared in the TYPEARGLIST might be used in the PARAMETERLIST, the RESULTTYPE and of course the body of the routine.

Generic routines can be overloaded both by TYPEARGLIST and PARAMETERLIST.

To call a generic routine you use the following syntax:

specialize IDENTIFIER<TYPELIST>[(PARAMETERS)]

For the TYPELIST the same rules apply as for specializing a generic type. If the routine is part of a type or variable or you're using a unit name to distinguish a generic routine you need to put that before the "specialize":

TYPENAME.specialize IDENTIFIER<TYPELIST>[(PARAMETERS)] VARIABLE.specialize IDENTIFIER<TYPELIST>[(PARAMETERS)] UNITNAME.specialize IDENTIFIER<TYPELIST>[(PARAMETERS)]

Calls to generic routines are normal factors so they can be used as such as the following example shows:

=== example begin ===

{$mode objfpc}
generic function Add<T>(aLeft, aRight: T): T;
begin
   Result := aLeft + aRight;
end;
begin
   Writeln(specialize Add<String>('Generic ', 'routines') + specialize
Add<String>(' with ', 'Free Pascal'));
end.

=== example end ===

Delphi compatibility:

Of course this future is also implemented in a Delphi-compatible way for the Delphi modes. There the syntax for declaring a generic function is like this:

[class] (procedure|function) IDENTIFIER<TYPELIST>[(PARAMETERLIST)][:
RETURNTYPE]; MODIFIERS;

So merely the "generic" keyword is missing. This is analogous when calling a generic routine:

IDENTIFIER<TYPELIST>[(PARAMETERS)]

Because of the missing "specialize" keyword that mark specializations complex expressions *do not* work in mode Delphi yet. So assignments of function results are okay, but other than that you'll likely encounter compiler errors.

Please note that unlike Delphi we *do* support global generic functions/procedures even in Delphi mode.

Limitations/ToDos:

The feature is not yet finished and there are some limitations that yet need to be overcome or parts that simply don't work yet, this includes, but is not necessarily limited to:

- support for complex expressions in Delphi modes (applies to type specializations as well)
- support for pointers to generic routines (this will currently result in errors at the best case and internal errors or exceptions at the worst)
- support for the return value in modes without support for "Result"
- support for nested generics, most importantly generic methods inside generic classes

So please test and report any bugs in the bug tracker. Questions can of course be asked here on the mailing list (no, this feature won't be part of FPC 3.0.0).

Regards,
Sven

Reply by email

silvioprog
to Sven Barth

Congratulations! I'm anxiously to test it! :-D

Sven Barth wrote 94 lines
Hello together! I'm pleased to finally announce the addition of generic functions, procedures and methods (collectively called "routines") to Free Pascal which allows writing type safe methods that can be used for multiple types. Syntax: The syntax for declaring a generic routine is similar to declaring a normal routine and also includes support for class methods: generic [class] (procedure|function) IDENTIFIER<TYPEARGLIST>[(PARAMETERLIST)][: RESULTTYPE]; MODIFIERS; For the TYPEARGLIST the same rules apply as for generic types. Type parameters declared in the TYPEARGLIST might be used in the PARAMETERLIST, the RESULTTYPE and of course the body of the routine. Generic routines can be overloaded both by TYPEARGLIST and PARAMETERLIST. To call a generic routine you use the following syntax: specialize IDENTIFIER<TYPELIST>[(PARAMETERS)] For the TYPELIST the same rules apply as for specializing a generic type. If the routine is part of a type or variable or you're using a unit name to distinguish a generic routine you need to put that before the "specialize": TYPENAME.specialize IDENTIFIER<TYPELIST>[(PARAMETERS)] VARIABLE.specialize IDENTIFIER<TYPELIST>[(PARAMETERS)] UNITNAME.specialize IDENTIFIER<TYPELIST>[(PARAMETERS)] Calls to generic routines are normal factors so they can be used as such as the following example shows: === example begin === {$mode objfpc} generic function Add<T>(aLeft, aRight: T): T; begin Result := aLeft + aRight; end; begin Writeln(specialize Add<String>('Generic ', 'routines') + specialize Add<String>(' with ', 'Free Pascal')); end. === example end === Delphi compatibility: Of course this future is also implemented in a Delphi-compatible way for the Delphi modes. There the syntax for declaring a generic function is like this: [class] (procedure|function) IDENTIFIER<TYPELIST>[(PARAMETERLIST)][: RETURNTYPE]; MODIFIERS; So merely the "generic" keyword is missing. This is analogous when calling a generic routine: IDENTIFIER<TYPELIST>[(PARAMETERS)] Because of the missing "specialize" keyword that mark specializations complex expressions *do not* work in mode Delphi yet. So assignments of function results are okay, but other than that you'll likely encounter compiler errors. Please note that unlike Delphi we *do* support global generic functions/procedures even in Delphi mode. Limitations/ToDos: The feature is not yet finished and there are some limitations that yet need to be overcome or parts that simply don't work yet, this includes, but is not necessarily limited to: - support for complex expressions in Delphi modes (applies to type specializations as well) - support for pointers to generic routines (this will currently result in errors at the best case and internal errors or exceptions at the worst) - support for the return value in modes without support for "Result" - support for nested generics, most importantly generic methods inside generic classes So please test and report any bugs in the bug tracker. Questions can of course be asked here on the mailing list (no, this feature won't be part of FPC 3.0.0). Regards, Sven
Silvio Clécio

Reply by email

leledumbo
to Sven Barth: Hello together! I'm pleased to finally announce the addition of generic functions,…

Congratulations! Nice work, Sven.
It's a bit weird to see the specialize in the middle of an expression but that's not a problem for me. The one that needs explanation is "complex expressions", what's the minimum requirement for an expression to be called complex for this case?

Sven Barth-2 wrote

Show quoted text 121 lines
Hello together! I'm pleased to finally announce the addition of generic functions, procedures and methods (collectively called "routines") to Free Pascal which allows writing type safe methods that can be used for multiple types. Syntax: The syntax for declaring a generic routine is similar to declaring a normal routine and also includes support for class methods: generic [class] (procedure|function) IDENTIFIER <TYPEARGLIST> [(PARAMETERLIST)][: RESULTTYPE]; MODIFIERS; For the TYPEARGLIST the same rules apply as for generic types. Type parameters declared in the TYPEARGLIST might be used in the PARAMETERLIST, the RESULTTYPE and of course the body of the routine. Generic routines can be overloaded both by TYPEARGLIST and PARAMETERLIST. To call a generic routine you use the following syntax: specialize IDENTIFIER <TYPELIST> [(PARAMETERS)] For the TYPELIST the same rules apply as for specializing a generic type. If the routine is part of a type or variable or you're using a unit name to distinguish a generic routine you need to put that before the "specialize": TYPENAME.specialize IDENTIFIER <TYPELIST> [(PARAMETERS)] VARIABLE.specialize IDENTIFIER <TYPELIST> [(PARAMETERS)] UNITNAME.specialize IDENTIFIER <TYPELIST> [(PARAMETERS)] Calls to generic routines are normal factors so they can be used as such as the following example shows: === example begin === {$mode objfpc} generic function Add <T> (aLeft, aRight: T): T; begin Result := aLeft + aRight; end; begin Writeln(specialize Add <String> ('Generic ', 'routines') + specialize Add <String> (' with ', 'Free Pascal')); end. === example end === Delphi compatibility: Of course this future is also implemented in a Delphi-compatible way for the Delphi modes. There the syntax for declaring a generic function is like this: [class] (procedure|function) IDENTIFIER <TYPELIST> [(PARAMETERLIST)][: RETURNTYPE]; MODIFIERS; So merely the "generic" keyword is missing. This is analogous when calling a generic routine: IDENTIFIER <TYPELIST> [(PARAMETERS)] Because of the missing "specialize" keyword that mark specializations complex expressions *do not* work in mode Delphi yet. So assignments of function results are okay, but other than that you'll likely encounter compiler errors. Please note that unlike Delphi we *do* support global generic functions/procedures even in Delphi mode. Limitations/ToDos: The feature is not yet finished and there are some limitations that yet need to be overcome or parts that simply don't work yet, this includes, but is not necessarily limited to: - support for complex expressions in Delphi modes (applies to type specializations as well) - support for pointers to generic routines (this will currently result in errors at the best case and internal errors or exceptions at the worst) - support for the return value in modes without support for "Result" - support for nested generics, most importantly generic methods inside generic classes So please test and report any bugs in the bug tracker. Questions can of course be asked here on the mailing list (no, this feature won't be part of FPC 3.0.0). Regards, Sven _______________________________________________ fpc-pascal maillist - http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal

Reply by email

Sven Barth
to leledumbo
leledumbo wrote 5 lines
Congratulations! Nice work, Sven. It's a bit weird to see the specialize in the middle of an expression but that's not a problem for me. The one that needs explanation is "complex expressions", what's the minimum requirement for an expression to be called complex for this case?

I'll simply quote my answer to Maciej on fpc-devel:

Just take the example that I posted and convert to mode Delphi:

=== code begin ===

{$mode delphi}
function Add<T>(aLeft, aRight: T): T;
begin
   Result := aLeft + aRight;
end;
begin
   Writeln(Add<String>('Generic ', 'routines') + Add<String>(' with ',
'Free Pascal'));
end.

=== code end ===

The compiler will error out like this:

=== output begin ===

Target OS: Linux for i386
Compiling ./fpctests/tgenroutines.pp
tgenroutines.pp(9,47) Fatal: Syntax error, ")" expected but "+" found Fatal: Compilation aborted

=== output end ===

What will work however is this:

=== example begin ===

{$mode delphi}
function Add<T>(aLeft, aRight: T): T;
begin
   Result := aLeft + aRight;
end;
var
   s1, s2: String;
begin
   s1 := Add<String>('Generic ', 'routines');
   s2 := Add<String>(' with ', 'Free Pascal');
   Writeln(s1, s2);
end.

=== example end ===

So basically just use it as a single factor (or statement) and you'll be fine until I've improved that.

Regards,
Sven

Reply by email

Travis Siegel
to Sven Barth: Hello together! I'm pleased to finally announce the addition of generic functions,…

Who do I talk to about fpc 3.0.0-rc2 not compiling on a raspberry pi? I did reply to one email about an rc2 candidate, but got no reply, (and it turns out I didn't have the latest available version at the time anyhow) so the error changed, but it's still not compiling completely, though it sure does get a good long way into the compile process. I've not tried it again since Nov 4, but somebody needs to know, so it can be fixed (I'd think).

Reply by email

Mark Morgan Lloyd
to Travis Siegel
Travis Siegel wrote 7 lines
Who do I talk to about fpc 3.0.0-rc2 not compiling on a raspberry pi? I did reply to one email about an rc2 candidate, but got no reply, (and it turns out I didn't have the latest available version at the time anyhow) so the error changed, but it's still not compiling completely, though it sure does get a good long way into the compile process. I've not tried it again since Nov 4, but somebody needs to know, so it can be fixed (I'd think).

DO NOT tack it onto somebody else's thread, particularly a core developer who is announcing a significant feature that he's put a lot of work into.

Start a new thread, and make sure you tell us what compiler etc. you're starting with.

Mark Morgan Lloyd markMLl .AT. telemetry.co .DOT. uk [Opinions above are the author's, not those of his employers or colleagues]

Reply by email

Anthony Walter
to leledumbo

Here are a few tests ...

Test 1 works:

function Swap<T>(var A, B: T): T;
var
  C: T;
begin
  C := A;
  A := B;
  B := C;
end;

Example 1 usage:

Swap<Integer>(I, J); // J now holds I value, I value holds J

Test 2 works but with notes:

function Find<T>: T;
var
  I: Integer;
begin
  for I := 0 to Form1.ComponentCount - 1 do
  if Form1.Components[I].InheritsFrom(T) then
  Exit(T(Form1.Components[I]));
  Result := nil;
end;

Example 2 usage:

Find<TShape>.Brush.Color := clRed;

Notes:

Cannot use 'as' or 'is' operators. I had to use

  if Form1.Components[I].InheritsFrom(T) then
  Exit(T(Form1.Components[I]));

Instead of

  if Form1.Components[I] is T then
  Exit(Form1.Components[I] as T);

I could not use this as a method, for example:

function TForm1.Find<T>: T;

Question:

Will it be possible to use type constraints?

function TForm1.Find<T: TComponent>: T;
or
function Find<T: TComponent>: T;

Test 3 does not work:

function Compare<T>(constref A, B: T): Integer;
begin
  if A > B then
    Result := 1
  else if B > A then
  Result := -1
  else
    Result := 0;
end;
procedure Test3;
var
  Items: TArrayList<Single>; // See Codebot.System
begin
  Randomize;
  Items.Push(Random * 100);
  Items.Push(Random * 100);
  Items.Push(Random * 100);
  Items.Push(Random * 100);
  // compile fails below
  Items.Sort(soDescend, @Compare<Single>); // cannot take address of
Compare<Single>
end;

Reply by email

Sven Barth
to Anthony Walter
Anthony Walter wrote 27 lines
Test 2 works but with notes: function Find<T>: T; var I: Integer; begin for I := 0 to Form1.ComponentCount - 1 do if Form1.Components[I].InheritsFrom(T) then Exit(T(Form1.Components[I])); Result := nil; end; Example 2 usage: Find<TShape>.Brush.Color := clRed; Notes: Cannot use 'as' or 'is' operators. I had to use if Form1.Components[I].InheritsFrom(T) then Exit(T(Form1.Components[I])); Instead of if Form1.Components[I] is T then Exit(Form1.Components[I] as T);

Of course, because T is not an object (though one could argue that everywhere else regarding generics we are much less picky...). Nevertheless since you're in mode Delphi anyway, this wouldn't work in Delphi either.

I could not use this as a method, for example: function TForm1.Find<T>: T;

Please show the code and the error message, because here it works.

Show quoted text 7 lines
Question: Will it be possible to use type constraints? function TForm1.Find<T: TComponent>: T; or function Find<T: TComponent>: T;

Already works (and there you can use "as" and "is", though this would already work with a "TObject" or - Delphi compatible - "class" constraint). [Constraints themselves are already part of 3.0.0]

Show quoted text 24 lines
Test 3 does not work: function Compare<T>(constref A, B: T): Integer; begin if A > B then Result := 1 else if B > A then Result := -1 else Result := 0; end; procedure Test3; var Items: TArrayList<Single>; // See Codebot.System begin Randomize; Items.Push(Random * 100); Items.Push(Random * 100); Items.Push(Random * 100); Items.Push(Random * 100); // compile fails below Items.Sort(soDescend, @Compare<Single>); // cannot take address of Compare<Single> end;

As mentioned in my announcement this is a limitation I have yet to address.

Regards,
Sven

Reply by email

Anthony Walter
to Sven Barth

Sven, in the with regards to "of course", I think you have it reversed. Is and as were being used as a class in my test, not an object.

All examples in {$mode delphi}

if Form1.Components[I] is T then // does not compile in my test

while

if Form1.Components[I].Inheritsfrom(T) // did compile

In essence, I could not use "is" or "as". Here is a test:

function TestIsAs<T>(Item: TObject): T;
begin
  if Item is T then
  begin
    Exit(Item as T);
  end;
  Result := nil;
end;

Result - Error: Class or interface type expected, but got "T" (on line "if Item<error here> is T then")

With regards to constraints, I tried them previously, but went back to test them again realized that with class methods the constraint must be in the method declaration of the class only.

That is:

type
  TConstraintMethodTest = class
  public
    procedure SomeTest<T: TComponent>(Value: T);
  end;
// Below is wrong
procedure TConstraintMethodTest.SomeTest<T: TComponent>(Value: T);
begin
end;
// This works
procedure TConstraintMethodTest.SomeTest<T>(Value: T);
begin
end;

Reply by email

Anthony Walter
to Anthony Walter

More ...

// This works
function TestIsAs2<T: TObject>(Item: TObject): T;
begin
  if Item is T then
  begin
    Exit(Item as T);
  end;
  Result := nil;
end;

Now before you say "of course" :) I find it interesting because is and as are operators, just like > greater than and < less than are operators. In previous examples no constraints were needed to use operators.

Having said that, I am find with being forced to use a constraint with "as" and "is", because they are operators which cannot be overloaded.

Good work!

Reply by email

Anthony Walter
to Anthony Walter

Here is another test, a bit more complex. It fails when trying to pass a procedure reference to the Execute<T, A>. This is a bit different than trying to get a reference to a generic routine. Routines like ControlMove and ShapeColor and not generic.

{$mode delphi}
type
  TForEach<T, A> = procedure(Item: T; Arg: A);
// inside TMainForm declaration
procedure Execute<T: TComponent; A>(ForEach: TForEach<T, A>; Arg: A);
// implementation
procedure TMainForm.Execute<T, A>(ForEach: TForEach<T, A>; Arg: A);
var
  I: Integer;
begin
  for I := 0 to ComponentCount - 1 do
    if Components[I] is T then
      ForEach(Components[I] as T, Arg);
end;
procedure ControlMove(Item: TControl; Arg: Integer);
begin
  Item.Left := Arg;
end;
procedure ShapeColor(Item: TShape; Arg: TColor);
begin
  Item.Brush.Color := Arg;
end;
procedure TMainForm.ExecuteButtonClick(Sender: TObject);
begin
  // below fails after "Integer>" with Compilation raised exception
internally
  Execute<TControl, Integer>(ControlMove, Mouse.CursorPos.X);
  if ColorDialog.Execute then
    Execute<TShape, TColor>(ShapeColor, ColorDialog.Color);
end;

Reply by email

Anthony Walter
to che fou

Sven, it would seem you cannot currently cannot pass a generic T to resolve the type of another generic subroutine.

In this example, Test<T> cannot reuse T in its call to Min<T>. I believe this line should be legal "Value := Min<T>(A, B);" (see the code below for context)

Currently when you try to compile the unit GenericTest you'll receive a compiler error on line marked below. Would you like me to enter this into issue into mantis?

unit GenericTest;
{$mode delphi}
interface
function Min<T>(const A, B: T): T;
function Max<T>(const A, B: T): T;
procedure Test<T>(const A, B: T);
implementation
function Min<T>(const A, B: T): T;
// Error on line below, GenericTest.pas(14,1) Error: Internal error
200301231
begin
  if A > B then
    Result := A
  else
  Result := B;
end;
function Max<T>(const A, B: T): T;
begin
  if A > B then
    Result := A
  else
    Result := B;
end;
procedure Test<T>(const A, B: T);
var
  Value: T;
begin
  // This should be legal
  Value := Min<T>(A, B);
  WriteLn('The Min<T> of values, ', A, ' and ', B, ' are: ', Value);
  // As well as this
  Value := Max<T>(A, B);
  WriteLn('The Max<T> of values, ', A, ' and ', B, ' are: ', Value);
end;
end.

Reply by email

Sven Barth
to Anthony Walter
"Anthony Walter" wrote
Sven, it would seem you cannot currently cannot pass a generic T to

resolve the type of another generic subroutine.

In this example, Test<T> cannot reuse T in its call to Min<T>. I believe
this line should be legal "Value := Min<T>(A, B);" (see the code below for
context)
Currently when you try to compile the unit GenericTest you'll receive a

compiler error on line marked below. Would you like me to enter this into issue into mantis?

Yes, please. That's definitely something that should work :)

Regards,
Sven

Reply by email

DougC
to Sven Barth: resolve the type of another generic subroutine. this line should be legal "Value :=…

But first fix the code so the test is correct. (The Min function is exactly the same as the Max function. Min should have A &lt; B, not A &gt; B.)

---- On Tue, 24 Nov 2015 08:31:08 -0500 Sven Barth &lt;;wrote ----

Am 24.11.2015 14:26 schrieb "Anthony Walter" &lt;;:

 &gt;

&gt; Sven, it would seem you cannot currently cannot pass a generic T to resolve the type of another generic subroutine.

 &gt;
 &gt; In this example, Test&lt;T&gt; cannot reuse T in its call to Min&lt;T&gt;. I believe this line should be legal "Value := Min&lt;T&gt;(A, B);" (see the code below for context)
 &gt;

&gt; Currently when you try to compile the unit GenericTest you'll receive a compiler error on line marked below. Would you like me to enter this into issue into mantis?

Yes, please. That's definitely something that should work :)

Regards,

Sven

Reply by email

leledumbo
to Anthony Walter: Sven, in the with regards to "of course", I think you have it reversed. Is and as were…
With regards to constraints, I tried them previously, but went back to test

them again realized that with class methods the constraint must be in the method declaration of the class only.

This needs to be documented, I did the same mistake as you.

Reply by email

leledumbo
to leledumbo: fine until I've improved that. Roger that. View this message in context:…

Sven, I try the generic method inside one of my libraries and it generates internal error 200204175, but I can't reproduce it with smaller programs. How do you think I can help you to see it? It's currently a private project in bitbucket (just because I don't want people to use it before it's API stable) but I can give you access to it if there's no other way.

leledumbo wrote

> So basically just use it as a single factor (or statement) and you'll be fine until I've improved that. Roger that.

Reply by email

Sven Barth
to leledumbo
"leledumbo" wrote
Sven, I try the generic method inside one of my libraries and it generates internal error 200204175, but I can't reproduce it with smaller programs. How do you think I can help you to see it? It's currently a private

project

in bitbucket (just because I don't want people to use it before it's API stable) but I can give you access to it if there's no other way.

I'll check what that internal error means and then I might be able to tell you what you'll have to look out for to reproduce it in a smaller example.

Regards,
Sven

Reply by email