Sign in Join

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

Mailing lists / fpc-pascal / 2022-04

Feature announcement: implicit generic function specializations

48 messages · 11 people · –

Reply by email Subscribe to this list Your subscription

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

Sven Barth

Dear FPC community,

The FPC developers are pleased to announce the implementation of a new feature: implicit generic function specializations. This feature was implemented by Ryan Joseph, so thank you very much, Ryan.

This feature allows you to use generic routines (functions, procedures, methods) without explicitely specializing them (“<…>” in Delphi modes and “specialize …<…>” in non-Delphi modes) as long as the compiler can determine the correct parameter types for the generic.

This feature is enabled with the modeswitch
ImplicitFunctionSpecialization and is for now not enabled by default as this has the potential to break existing code.

Assume you have the following function:

=== code begin ===

generic function Add<T>(aArg1, aArg2: T): T;
begin
   Result := aArg1 + aArg2;
end;

=== code end ===

Up to now you could only use this function as follows:

=== code begin ===

SomeStr := specialize Add<String>('Hello', 'World');
SomeInt := specialize Add<LongInt>(2, 5);

=== code end ===

However with implicit function specializations enabled you can also use it as follows:

=== code begin ===

SomeStr := Add('Hello', 'World');
SomeInt := Add(2, 5);

=== code end ===

The compiler will automatically determine the type of the generic parameters based on the parameters you pass in (this is always done left to right). Depending on the passed in parameters (especially if you're using constant values like in the example instead of variables) the compiler might however pick a different type than you expected. You can enforce a specific type by either explicitely specializing the method as before or by inserting a type cast. In the example above the compile will specialize the call with the parameters “2, 5” using an 8-bit signed type (Pascal prefers signed types) instead of a LongInt as in the explicit specialization. If you use “LongInt(2), 5” as parameters then the compiler will pick that instead, however with “2, LongInt(5)” it will still pick an 8-bit type, because the parameter types are determined left to right.

If there exists a non-generic overload for which the parameters types match exactly, the compiler will pick that instead of specializing something anew. So assume you also have the following function in scope:

=== code begin ===

function Add(aArg1, aArg2: LongInt): LongInt;
begin
   Result := aArg1 + aArg2;
end;

=== code end ===

In the case of “Add(2, 5)” the compiler will *not* pick the non-generic function, because it determines that an 8-bit type is enough, however if you use “Add(LongInt(2), 5)” the compiler will pick the non-generic function.

Aside from simple parameters the compiler also supports arrays and function/method variables:

=== code begin ===

generic function ArrayFunc<T>(aArg: specialize TArray<T>): T;
var
   e: T;
begin
   Result := Default(T);
   for e in aArg do
     Result := Result + e;
end;
type
   generic TTest<T> = function(aArg: T): T;
generic function Apply<T>(aFunc: specialize TTest<T>; aArg: T): T;
begin
   Result := aFunc(aArg);
end;
function StrFunc(aArg: String): String;
begin
   Result := UpCase(aArg);
end;
function NegFunc(aArg: LongInt): LongInt;
begin
   Result := - aArg;
end;
begin
   Writeln(ArrayFunc([1, 2, 3])); // will write 6
   Writeln(ArrayFunc(['Hello', 'FPC', 'World'])); // will write
HelloFPCWorld
   Writeln(Apply(@StrFunc, 'Foobar')); // will write FOOBAR
   Writeln(Apply(@NegFunc, 42)); // will write -42
end.

=== code end ===

There are of course a few restrictions for this feature:
- all generic parameters must be used in the declaration of the routine (implementation only type parameters are not allowed)
- all parameters that have a generic type must not be default parameters, they need to be used in the call or their type must have been fixed by a parameter further left (as currently default values for parameters of a generic type are not supported this is not much of a restriction, but should that change (e.g. Default(T)) then this restriction will apply)
- the generic routine must not have constant generic parameters (this might be extended in the future with e.g. static arrays or file types, but for now this restriction stands)
- the result type is not taken into account, so if only the result type of a routine is generic then an implicit specialization does not work either
- function/method pointers to implicit specializations are not yet supported (pointers to explicit specializations are not yet supported either; once this changes the former will change as well)
- the compiler will silently discard generic functions that it can't specialize the *declaration* of; however if the declaration can be specialized correctly, but for whatever reason the *implementation* can not then this will trigger a compilation error

This feature is by and in itself Delphi compatible however there might be differences in what FPC can implicitely specialize and what Delphi can. Especially if Delphi can specialize something that FPC can not, this should be reported.

With kind regards,
Sven/Sarah Barth

Mattias Gaertner
to Sven Barth: The FPC developers are pleased to announce the implementation of a new feature: implicit…
[...] This feature is enabled with the modeswitch ImplicitFunctionSpecialization and is for now not enabled by default as this has the potential to break existing code.

Sad. This feature really makes generic functions shine.

Show quoted text 16 lines
[...] generic function ArrayFunc<T>(aArg: specialize TArray<T>): T; [...] type   generic TTest<T> = function(aArg: T): T; generic function Apply<T>(aFunc: specialize TTest<T>; aArg: T): T; [...] begin   Writeln(ArrayFunc([1, 2, 3])); // will write 6   Writeln(ArrayFunc(['Hello', 'FPC', 'World'])); // will write HelloFPCWorld   Writeln(Apply(@StrFunc, 'Foobar')); // will write FOOBAR   Writeln(Apply(@NegFunc, 42)); // will write -42 end.

Mind boggling. :)

Mattias

Hairy Pixels
to Mattias Gaertner
Show quoted text 7 lines
On Apr 21, 2022, at 3:48 AM, Mattias Gaertner via fpc-pascal wrote: > Writeln(Apply(@StrFunc, 'Foobar')); // will write FOOBAR > Writeln(Apply(@NegFunc, 42)); // will write -42 > end. Mind boggling. :)

This was actually your idea if I remember correctly. :) I think you said even Delphi doesn’t do this so I challenged myself to get it done.

Regards,
Ryan Joseph

Michael Van Canneyt
to Hairy Pixels
Hairy Pixels wrote 11 lines
> On Apr 21, 2022, at 3:48 AM, Mattias Gaertner via fpc-pascal wrote: > >> Writeln(Apply(@StrFunc, 'Foobar')); // will write FOOBAR >> Writeln(Apply(@NegFunc, 42)); // will write -42 >> end. > > Mind boggling. :) This was actually your idea if I remember correctly. :) I think you said even Delphi doesn’t do this so I challenged myself to get it done.

Delphi does it, but only for methods of a class.

AFAIK delphi does not support generic "plain" functions.

Michael.

Mattias Gaertner
to Michael Van Canneyt

On Thu, 21 Apr 2022 10:00:46 +0200 (CEST)
Michael Van Canneyt via fpc-pascal

Show quoted text 19 lines
On Thu, 21 Apr 2022, Hairy Pixels via fpc-pascal wrote: > > >> On Apr 21, 2022, at 3:48 AM, Mattias Gaertner via fpc-pascal >> wrote: >>> Writeln(Apply(@StrFunc, 'Foobar')); // will write FOOBAR >>> Writeln(Apply(@NegFunc, 42)); // will write -42 >>> end. >> >> Mind boggling. :) > > This was actually your idea if I remember correctly. :) I think you > said even Delphi doesn’t do this so I challenged myself to get it > done. Delphi does it, but only for methods of a class. AFAIK delphi does not support generic "plain" functions.

Delphi has generic methods and still no generic "plain" functions.

Delphi 11 still does not support *nested* implicit specializations. For example the Apply with methods gives in Delphi:

E2010 Incompatible types 'T' and 'String'
F2084 Internal Error: AV10D0B8D2(10CB0000)-R800000000-0

Mattias

Hairy Pixels
to Mattias Gaertner

If you want to pass a pointer to ^T in a generic function is there anyway safe to do this currently? Pascal doesn’t allow ^ types in function arguments (why?) and generics don’t seems to support pointers either (why?).

  generic TValues<T> = array[0..0] of T;
  generic PValues<T> = ^specialize TValues<T>;

I guess the only thing to do is use a untyped pointer and cast it to the correct type inside the function declaration?

For example here is a generic QuickSort function which operates on any array of T.

type
  generic TComparator<T> = function (left: T; right: T; context: pointer): integer;
generic procedure QuickSort<T>(_values: pointer; first, last: LongInt; comparator: specialize TComparator<T>);
type
  TValues = array[0..0] of T;
  PValues = ^TValues;
var
  pivot,j,i: integer;
  temp: T;
  values: PValues absolute _values;

Regards,
Ryan Joseph

Michael Van Canneyt
to Hairy Pixels: If you want to pass a pointer to ^T in a generic function is there anyway safe to do this…
Hairy Pixels wrote
If you want to pass a pointer to ^T in a generic function is there anyway safe to do this currently? Pascal doesn’t allow ^ types in function arguments (why?)

Because it is a type definition and you can't put type definition in a function declaration. e.g. function b (a : record x,y : byte end) : byte; also does not work.

Michael.

denisgolovan
to Hairy Pixels: If you want to pass a pointer to ^T in a generic function is there anyway safe to do this…
Show quoted text 24 lines
If you want to pass a pointer to ^T in a generic function is there anyway safe to do this currently? Pascal doesn’t allow ^ types in function arguments (why?) and generics don’t seems to support pointers either (why?). generic TValues<T> = array[0..0] of T; generic PValues<T> = ^specialize TValues<T>; I guess the only thing to do is use a untyped pointer and cast it to the correct type inside the function declaration? For example here is a generic QuickSort function which operates on any array of T. type generic TComparator<T> = function (left: T; right: T; context: pointer): integer; generic procedure QuickSort<T>(_values: pointer; first, last: LongInt; comparator: specialize TComparator<T>); type TValues = array[0..0] of T; PValues = ^TValues; var pivot,j,i: integer; temp: T; values: PValues absolute _values; Regards, Ryan Joseph

Not that pretty, but I use something like:

==============================================
generic TPtr<T> = record
    public
      type P = ^T;
    public
      ptr: P;
  end;
generic function Ref<T>(out r: specialize TPtr<T>.P):boolean;
==============================================

Denis Golovan

Rainer Stratmann
to Sven Barth: The FPC developers are pleased to announce the implementation of a new feature: implicit…
Sven Barth wrote
This feature is enabled with the modeswitch ImplicitFunctionSpecialization and is for now not enabled by default as this has the potential to break existing code.

How many percent of the users need this feature?
Is it a feature that is a must?
Can everyone get also a solution without the feature? Does it justify the risk of the whole language (has the potential to break existing code)?

Hairy Pixels
to Rainer Stratmann
Show quoted text 7 lines
On Apr 22, 2022, at 8:48 PM, Rainer Stratmann via fpc-pascal wrote: How many percent of the users need this feature? Is it a feature that is a must? Can everyone get also a solution without the feature? Does it justify the risk of the whole language (has the potential to break existing code)?

It’s like everything else in programming languages.

1) you can specialize the function manually so implicit specialization is not needed.
2) you can duplicate functions and change types manually so generic functions are not needed.
3) you can program in assembly so high-level languages are needed.
4) etc… :)

Joking aside it just makes for less code and more readable code (in my opinion).

Regards,
Ryan Joseph

Rainer Stratmann
to Hairy Pixels
Hairy Pixels wrote 13 lines
> On Apr 22, 2022, at 8:48 PM, Rainer Stratmann via fpc-pascal It’s like everything else in programming languages. 1) you can specialize the function manually so implicit specialization is not needed. 2) you can duplicate functions and change types manually so generic functions are not needed. 3) you can program in assembly so high-level languages are needed. 4) etc… :) Joking aside it just makes for less code and more readable code (in my opinion).

From assembly to high-level language there is a huge step. From high-level language to implicit generic function specializations it is a little step regarding the benefits.

In my opinion it makes everything more complicated. My mind refuses to read the description of the new feature.

But mostly I am worried because of the statement "has the potential to break existing code".

Howard Page-Clark
to Rainer Stratmann
Rainer Stratmann wrote 11 lines
Am Freitag, 22. April 2022, 17:27:33 CEST schrieb Hairy Pixels via fpc-pascal: >> On Apr 22, 2022, at 8:48 PM, Rainer Stratmann via fpc-pascal From assembly to high-level language there is a huge step. From high-level language to implicit generic function specializations it is a little step regarding the benefits. In my opinion it makes everything more complicated. My mind refuses to read the description of the new feature. But mostly I am worried because of the statement "has the potential to break existing code".

Rainer,

We assume this is a language feature you will not use in your code, which is fine.

However, it is a cause for celebration among those who do welcome such syntax sugar, and will be using the feature to improve the size and readability of their code.

kind regards,

Howard

Sven Barth
to Rainer Stratmann: How many percent of the users need this feature? Is it a feature that is a must? Can…
Rainer Stratmann wrote 5 lines
Am Mittwoch, 20. April 2022, 19:15:15 CEST schrieb Sven Barth via fpc-pascal: > This feature is enabled with the modeswitch > ImplicitFunctionSpecialization and is for now not enabled by default as > this has the potential to break existing code. How many percent of the users need this feature?

We don't deal in percentages, however it reduces the amount of typing required to write code with a lot of specializations (in theory an IDE like Lazarus *could* help here as well however).

Is it a feature that is a must?

It's only syntactic sugar, so *normally* a feature as this wouldn't be really be given much of a thought, however Delphi supports it as well.

Can everyone get also a solution without the feature?

Yes, by simply doing the specializations as usual (e.g. "specialize Add<String>('Hello', 'World')" instead of "Add('Hello', 'World')"). As said, this is pure syntactic sugar.

Please note however that sometimes we also introduce features that you can't workaround that will also require changes to code if that feature in question is used (mainly by enabling the modeswitch). The RTTI attributes come to mind, cause then the directive clause for function directives can no longer be used if you want to use them:

=== code begin ===

{$mode objfpc}
{$modeswitch prefixedattributes}
function Foobar: LongInt; [stdcall, inline]; // this part is invalid
//function Foobar: LongInt; stdcall; inline; // and needs to be written
like this
begin
end;
begin
end.

=== code end ===

Does it justify the risk of the whole language (has the potential to break existing code)?

That is why it's behind a modeswitch and not enabled by default, so that it's in control of the user whether they want this or not. The problem space is rather small, but it nevertheless exists: you need to have a normal routine overloaded with a generic routine (for example across different units) and the compiler must decide to do an implicit specialization instead  of using the non generic routine (as it does without the feature enabled / before the feature was implemented), but the specialization of that function then needs to fail, because the generic routine can't be used with the type the compiler picked

Regards,
Sven

Martin Frb
to Sven Barth: The FPC developers are pleased to announce the implementation of a new feature: implicit…
Sven Barth wrote 9 lines
This feature allows you to use generic routines (functions, procedures, methods) without explicitely specializing them (“<…>” in Delphi modes and “specialize …<…>” in non-Delphi modes) as long as the compiler can determine the correct parameter types for the generic. This feature is enabled with the modeswitch ImplicitFunctionSpecialization and is for now not enabled by default as this has the potential to break existing code.

One more step by Delphi to remove type safety.
IMHO a good option would have been to allow specializing multiple overloads in a single statement. But since Embarcadero has decided otherwise....

I did explore what happens if I throw different types at it, and see how the current implementation deals with this (what I call) lack of type safety.
And also asked the question, how should it act?  (Because the current behaviour is new, expected to need fixes, and can obviously be fixed).

Show quoted text 14 lines
Assume you have the following function:   generic function Add<T>(aArg1, aArg2: T): T; ... Up to now you could only use this function as follows: SomeStr := specialize Add<String>('Hello', 'World'); SomeInt := specialize Add<LongInt>(2, 5); However with implicit function specializations enabled you can also use it as follows: SomeStr := Add('Hello', 'World'); SomeInt := Add(2, 5);

So what happens if:

var
   b: Byte;
   c: Cardinal;
begin
   Add(b, c);

Well, I tested: It uses the type of the first Param. So it calls a function for both param of type Byte. The cardinal argument is converted. (potentially truncated).

If you use numeric constants:
  writeln('   0', Add(0, 0) );      // ShortInt   writeln('1000', Add(1000, 1000) );  // SmallInt   writeln('100K', Add(100000, 100000) );  // Integer

So then, if you try to fix   " Add(b, c)" by checking that b and c have the same type => "Add(c, 0)" will fail => because 0 is ShortInt and not cardinal.

----------------------------
I created a little test program (see bottom of mail).

And it gives a strange warning:

Compile Project, Target:
C:\Users\martin\AppData\Local\Temp\project1.exe: Success, Warnings: 1, Hints: 3
project1.lpr(67,52) Warning: Range check error while evaluating constants (100000 must be between -128 and 127)
project1.lpr(41,18) Hint: Local proc "Add$1" is not used 72 lines compiled, 0.2 sec, 105136 bytes code, 5476 bytes data

Line 41 is the declaration of the generic
    generic function Add<T>(aArg1, aArg2: T): T;

So why does it generate "Add$1" if it does not use it? (Or rather why does it warn, if this is some internal details?)

--------------------------------
And if you add to the app (at the top)

function Add(aArg1, aArg2: Int64): Int64; overload;
begin
//  write(' Int64 overload ');
   Result := aArg1 + aArg2;
end;
Then the line  (Shortint, because the first param is ShortInt)
  writeln('#### 1,CK = ',Add(0, 100000) ); // ShortInt

Will no longer call the specialized function, but instead use the int64 version.

If you comment all "Add(...)" calls, except that one => Then you get an additional hint: "project1.lpr(81,41) Hint: Local proc "Add$1$crc9AB0BCED" is not used"
So then that very line generates a specialized version for the call, and then the compiler does not use it.

Yet if INSTEAD of adding the hardcoded Int64 variant (as given above), I let the compile create a Int64 version by adding
    writeln('#### 1,CK = ',Add(int64(0), 100000) ); // ShortInt before the line above... Well the line above will not use that int64 version, but use its own specialization...

program Project1;
{$mode objfpc}
{$ModeSwitch ImplicitFunctionSpecialization }
{//$H+}
uses SysUtils;
Procedure Foo(a: Integer; out r: ShortInt);
begin
   write(' ShortInt ');
   r := a;
end;
Procedure Foo(a: Integer; out r: SmallInt);
begin
   write(' SmallInt ');
   r := a;
end;
Procedure Foo(a: Integer; out r: Byte);
begin
   write(' Byte ');
   r := a;
end;
Procedure Foo(a: Integer; out r: Word);
begin
   write(' Word ');
   r := a;
end;
Procedure Foo(a: Integer; out r: Cardinal);
begin
   write(' Cardinal ');
   r := a;
end;
Procedure Foo(a: Integer; out r: Integer);
begin
   write(' Integer ');
   r := a;
end;
Procedure Foo(a: Integer; out r: Int64);
begin
   write(' Int64 ');
   r := a;
end;
Procedure Foo(a: Integer; out r: AnsiString);
begin
   write(' Ansi ');
   r := ' _A:'+IntToStr(a);
end;
Procedure Foo(a: Integer; out r: ShortString);
begin
   write(' Short ');
   r := ' _S:'+IntToStr(a);
end;
generic function Add<T>(aArg1, aArg2: T): T;
var x: T;
begin
   Foo(SizeOf(T), x);
   {$R-}
   Result := aArg1 + aArg2 + x;
end;
var
   b: byte;
   c: cardinal;
begin
   b := 0;
   c := 0;
   writeln('####    B = ',Add(b, b) );
   writeln('####    C = ',Add(c, c) );
   writeln('####    0 = ',Add(0, 0) );  // ShortInt
   writeln('#### 1000 = ',Add(1000, 1000) );  // SmallInt
   writeln('#### 100K = ',Add(100000, 100000) ); // Integer
   writeln;
   writeln('####  c,b = ',Add(c, b) ); // Cardinal
   writeln('####  b,c = ',Add(b, c) ); // Byte
   writeln('#### CK,1 = ',Add(100000, 1) ); // Integer
   {$R-}
   writeln('#### 1,CK = ',Add(0, 100000) ); // ShortInt
   writeln('#### Str  = ',Add('x1', 'x1') );
   readln;
end.
Mattias Gaertner
to Martin Frb
Show quoted text 6 lines
[...] I did explore what happens if I throw different types at it, and see how the current implementation deals with this (what I call) lack of type safety. And also asked the question, how should it act?  (Because the current behaviour is new, expected to need fixes, and can obviously be fixed).

See Sven's first mail.

Show quoted text 12 lines
[...] So what happens if: var   b: Byte;   c: Cardinal; begin   Add(b, c); Well, I tested: It uses the type of the first Param. So it calls a function for both param of type Byte. The cardinal argument is converted. (potentially truncated).

Yes, as explained by Sven.

Show quoted text 8 lines
If you use numeric constants:   writeln('   0', Add(0, 0) );      // ShortInt   writeln('1000', Add(1000, 1000) );  // SmallInt   writeln('100K', Add(100000, 100000) );  // Integer So then, if you try to fix   " Add(b, c)" by checking that b and c have the same type => "Add(c, 0)" will fail => because 0 is ShortInt and not cardinal.

Why is that a fail?

Show quoted text 10 lines
---------------------------- I created a little test program (see bottom of mail). And it gives a strange warning: Compile Project, Target: C:\Users\martin\AppData\Local\Temp\project1.exe: Success, Warnings: 1, Hints: 3 project1.lpr(67,52) Warning: Range check error while evaluating constants (100000 must be between -128 and 127)

Add(0, 100000)
Correct range error.

project1.lpr(41,18) Hint: Local proc "Add$1" is not used 72 lines compiled, 0.2 sec, 105136 bytes code, 5476 bytes data Line 41 is the declaration of the generic    generic function Add<T>(aArg1, aArg2: T): T;

Maybe related to
https://gitlab.com/freepascal.org/fpc/source/-/issues/39675

[...]

Mattias

Mattias Gaertner
to Martin Frb: One more step by Delphi to remove type safety. IMHO a good option would have been to…
[...] Well, I tested: It uses the type of the first Param. So it calls a function for both param of type Byte. The cardinal argument is converted. (potentially truncated).

I agree that Delphi should have used a better algorithm, like checking the var/out arguments first and otherwise use the smallest integer that fits all related params. Same for strings and boolean. pas2js uses a similar algorithm.

In $mode Delphi fpc must use the Delphi algorithm. In other modes it could use a better algorithm.
If fpc would use the above algorithm in mode objfpc, the Add would work like FPC's add:

generic function Add<T>(a,b: T): T;
begin
  Result:=a+b;
end;

Add(aByte,aCardinal) -> cardinal

And you don't need to put a var/out param leftmost:

generic procedure Adding<T>(a,b: T; out c: T);
begin
  c:=a+b;
end;
Adding(3,4,aWord);

Mattias

Thomas Kurz
to Sven Barth: The FPC developers are pleased to announce the implementation of a new feature: implicit…

Thank you for continuously enhancing Free Pascal. Happy to see every new feature :)

Is there any work ongoing about overloaded properties? E.g.

property x: integer read GetX;
property x[aindex: integer]: integer write SetX;

I know that declaring the property only once and overloading the getters and setters work, but sometimes I wish to have different read/readwrite/write access for overloaded properties and this is not possible at the moment. Furthermore, code completion in Lazarus doesn't show the different parameters of overloaded properties.

Earlier message 155 lines
----- Original Message ----- From: Sven Barth via fpc-pascal To: Sent: Wednesday, April 20, 2022, 19:15:15 Subject: [fpc-pascal] Feature announcement: implicit generic function specializations Dear FPC community, The FPC developers are pleased to announce the implementation of a new feature: implicit generic function specializations. This feature was implemented by Ryan Joseph, so thank you very much, Ryan. This feature allows you to use generic routines (functions, procedures, methods) without explicitely specializing them (“<…>” in Delphi modes and “specialize …<…>” in non-Delphi modes) as long as the compiler can determine the correct parameter types for the generic. This feature is enabled with the modeswitch ImplicitFunctionSpecialization and is for now not enabled by default as this has the potential to break existing code. Assume you have the following function: === code begin === generic function Add<T>(aArg1, aArg2: T): T; begin Result := aArg1 + aArg2; end; === code end === Up to now you could only use this function as follows: === code begin === SomeStr := specialize Add<String>('Hello', 'World'); SomeInt := specialize Add<LongInt>(2, 5); === code end === However with implicit function specializations enabled you can also use it as follows: === code begin === SomeStr := Add('Hello', 'World'); SomeInt := Add(2, 5); === code end === The compiler will automatically determine the type of the generic parameters based on the parameters you pass in (this is always done left to right). Depending on the passed in parameters (especially if you're using constant values like in the example instead of variables) the compiler might however pick a different type than you expected. You can enforce a specific type by either explicitely specializing the method as before or by inserting a type cast. In the example above the compile will specialize the call with the parameters “2, 5” using an 8-bit signed type (Pascal prefers signed types) instead of a LongInt as in the explicit specialization. If you use “LongInt(2), 5” as parameters then the compiler will pick that instead, however with “2, LongInt(5)” it will still pick an 8-bit type, because the parameter types are determined left to right. If there exists a non-generic overload for which the parameters types match exactly, the compiler will pick that instead of specializing something anew. So assume you also have the following function in scope: === code begin === function Add(aArg1, aArg2: LongInt): LongInt; begin Result := aArg1 + aArg2; end; === code end === In the case of “Add(2, 5)” the compiler will *not* pick the non-generic function, because it determines that an 8-bit type is enough, however if you use “Add(LongInt(2), 5)” the compiler will pick the non-generic function. Aside from simple parameters the compiler also supports arrays and function/method variables: === code begin === generic function ArrayFunc<T>(aArg: specialize TArray<T>): T; var e: T; begin Result := Default(T); for e in aArg do Result := Result + e; end; type generic TTest<T> = function(aArg: T): T; generic function Apply<T>(aFunc: specialize TTest<T>; aArg: T): T; begin Result := aFunc(aArg); end; function StrFunc(aArg: String): String; begin Result := UpCase(aArg); end; function NegFunc(aArg: LongInt): LongInt; begin Result := - aArg; end; begin Writeln(ArrayFunc([1, 2, 3])); // will write 6 Writeln(ArrayFunc(['Hello', 'FPC', 'World'])); // will write HelloFPCWorld Writeln(Apply(@StrFunc, 'Foobar')); // will write FOOBAR Writeln(Apply(@NegFunc, 42)); // will write -42 end. === code end === There are of course a few restrictions for this feature: - all generic parameters must be used in the declaration of the routine (implementation only type parameters are not allowed) - all parameters that have a generic type must not be default parameters, they need to be used in the call or their type must have been fixed by a parameter further left (as currently default values for parameters of a generic type are not supported this is not much of a restriction, but should that change (e.g. Default(T)) then this restriction will apply) - the generic routine must not have constant generic parameters (this might be extended in the future with e.g. static arrays or file types, but for now this restriction stands) - the result type is not taken into account, so if only the result type of a routine is generic then an implicit specialization does not work either - function/method pointers to implicit specializations are not yet supported (pointers to explicit specializations are not yet supported either; once this changes the former will change as well) - the compiler will silently discard generic functions that it can't specialize the *declaration* of; however if the declaration can be specialized correctly, but for whatever reason the *implementation* can not then this will trigger a compilation error This feature is by and in itself Delphi compatible however there might be differences in what FPC can implicitely specialize and what Delphi can. Especially if Delphi can specialize something that FPC can not, this should be reported. With kind regards, Sven/Sarah Barth
Rainer Stratmann
to Sven Barth: We don't deal in percentages, however it reduces the amount of typing required to write…

Am Freitag, 22. April 2022, 19:53:34 CEST schrieben Sie:

Am 22.04.2022 um 15:48 schrieb Rainer Stratmann via fpc-pascal: > Am Mittwoch, 20. April 2022, 19:15:15 CEST schrieb Sven Barth via fpc-

pascal:

We don't deal in percentages, however it reduces the amount of typing required to write code with a lot of specializations (in theory an IDE like Lazarus *could* help here as well however).

Of course 'you' do. When I asked for a simple feature years ago it was refused. And there were several explanations why this feature is not necessary.

Sven Barth
to Martin Frb: One more step by Delphi to remove type safety. IMHO a good option would have been to…
Martin Frb wrote 17 lines
I created a little test program (see bottom of mail). And it gives a strange warning: Compile Project, Target: C:\Users\martin\AppData\Local\Temp\project1.exe: Success, Warnings: 1, Hints: 3 project1.lpr(67,52) Warning: Range check error while evaluating constants (100000 must be between -128 and 127) project1.lpr(41,18) Hint: Local proc "Add$1" is not used 72 lines compiled, 0.2 sec, 105136 bytes code, 5476 bytes data Line 41 is the declaration of the generic    generic function Add<T>(aArg1, aArg2: T): T; So why does it generate "Add$1" if it does not use it? (Or rather why does it warn, if this is some internal details?)

Add$1 is the symbol of the generic itself, *not* the specialization. But please report it, cause that definitely shouldn't be the case anyway, as the generic is after all used by the specializations (and I should compile with -vh more often...)

Show quoted text 15 lines
-------------------------------- And if you add to the app (at the top) function Add(aArg1, aArg2: Int64): Int64; overload; begin //  write(' Int64 overload ');   Result := aArg1 + aArg2; end; Then the line  (Shortint, because the first param is ShortInt)  writeln('#### 1,CK = ',Add(0, 100000) ); // ShortInt Will no longer call the specialized function, but instead use the int64 version.

Correct, because a matching non-generic variant is available and the compiler then picks that.

Show quoted text 6 lines
If you comment all "Add(...)" calls, except that one => Then you get an additional hint: "project1.lpr(81,41) Hint: Local proc "Add$1$crc9AB0BCED" is not used" So then that very line generates a specialized version for the call, and then the compiler does not use it.

The compiler essentially works like this: it generates a suitable set of overloads. This includes the non-generic routines as well as all generic routines for which suitable type parameters can be found. Out of this set the compiler will then pick the function it will finally use with a preference for non-generic functions (even if parameters might be a worse, though not incompatible match).

That said: if the compiler does not pick one of the specializations it should totally discard them, so please report a bug for this as well.

Show quoted text 7 lines
Yet if INSTEAD of adding the hardcoded Int64 variant (as given above), I let the compile create a Int64 version by adding     writeln('#### 1,CK = ',Add(int64(0), 100000) ); // ShortInt before the line above... Well the line above will not use that int64 version, but use its own specialization...

The compiler determines the generic type parameters for each call. In this case it will be Int64 due to the typecast. However that will not change what the other, previous Add calls use and it also won't affect one any following Add call, cause it will simply determine the set of elligible functions (and other implicit specializations as well as explicit specializaitons are *not* part of this) and then determine which one to call out of this set (the compiler might specialize the same generic function with the same generic type parameters multiple times then, but the part that's dealing with specializations in general will detect that and point to the existing symbol instead).

Regards,
Sven

Sven Barth
to Mattias Gaertner: On Fri, 22 Apr 2022 20:51:56 +0200 I agree that Delphi should have used a better…
Mattias Gaertner wrote 11 lines
On Fri, 22 Apr 2022 20:51:56 +0200 Martin Frb via fpc-pascal wrote: > [...] > Well, I tested: It uses the type of the first Param. So it calls a > function for both param of type Byte. The cardinal argument is > converted. (potentially truncated). I agree that Delphi should have used a better algorithm, like checking the var/out arguments first and otherwise use the smallest integer that fits all related params. Same for strings and boolean. pas2js uses a similar algorithm.

Considering that we're talking mainly about differences of width and precision (e.g. the differences between a string and a record are more clear) this can probably be improved without considering Delphi here, because this is nowhere documented... (our non-generic overload selection might lead to different results after all as well) But even if we only improve this for non-Delphi modes I agree that a suggestion like yours would be an improvement.

Regards,
Sven

Martin Frb
to Sven Barth: Add$1 is the symbol of the generic itself, *not* the specialization. But please report…
Sven Barth wrote 25 lines
Am 22.04.2022 um 20:51 schrieb Martin Frb via fpc-pascal: > > > So why does it generate "Add$1" if it does not use it? (Or rather why > does it warn, if this is some internal details?) Add$1 is the symbol of the generic itself, *not* the specialization. But please report it, cause that definitely shouldn't be the case anyway, as the generic is after all used by the specializations (and I should compile with -vh more often...) > => Then you get an additional hint: "project1.lpr(81,41) Hint: Local > proc "Add$1$crc9AB0BCED" is not used" > So then that very line generates a specialized version for the call, > and then the compiler does not use it. The compiler essentially works like this: it generates a suitable set of overloads. This includes the non-generic routines as well as all generic routines for which suitable type parameters can be found. Out of this set the compiler will then pick the function it will finally use with a preference for non-generic functions (even if parameters might be a worse, though not incompatible match). That said: if the compiler does not pick one of the specializations it should totally discard them, so please report a bug for this as well.

Done
https://gitlab.com/freepascal.org/fpc/source/-/issues/39684 https://gitlab.com/freepascal.org/fpc/source/-/issues/39685

Martin Frb
to Martin Frb

Possible one more / Though more of an optimization.

If the code has multiple
    Add(Int64(0), Int64(0));
then they will all use the same procedure (just break in the debugger and check the CRC).

But if  one specialization is explicit and the other implicit then 2 identical (as far as I can tell with -al ) functions (with diff CRC) are created.

There is however a subtle difference in the generate asm. The explicit specialization has comment for the source code # [6] begin
...
# [7] Result := aArg1 + aArg2;
...

The implicit does not have those.
Actually I checked that a bit deeper. The comment occur in (and only in) the first specialization (implicit or explicit).
All other specialization are without comments for the source.

program Project1;
{$mode objfpc}
{$ModeSwitch ImplicitFunctionSpecialization }
generic function Add<T>(aArg1, aArg2: T): T;
begin
   Result := aArg1 + aArg2;
end;
begin
   specialize Add<Int64>(Int64(0), Int64(0));  // No Hint
   Add(Int64(0), Int64(0));
end.
Hairy Pixels
to Martin Frb: Done https://gitlab.com/freepascal.org/fpc/source/-/issues/39684…
Show quoted text 5 lines

It looks like I need to do some cleanup phase. I can’t remember how it works right now but I think I made the specialization so that it could be considered in overloading but after it was used it needs to be marked so hints aren’t given or deleted entirely (probably at the end of the unit if there is someway to know it was never used). Sven may have some ideas on how this works so feel free to drop them in the Gitlab issue.

Regards,
Ryan Joseph

Sven Barth
to Martin Frb: Possible one more / Though more of an optimization. If the code has multiple    …
Martin Frb wrote 10 lines
Possible one more / Though more of an optimization. If the code has multiple     Add(Int64(0), Int64(0)); then they will all use the same procedure (just break in the debugger and check the CRC). But if  one specialization is explicit and the other implicit then 2 identical (as far as I can tell with -al ) functions (with diff CRC) are created.

I would need to check why it generates two then. Please report this as well.

Show quoted text 11 lines
There is however a subtle difference in the generate asm. The explicit specialization has comment for the source code # [6] begin ... # [7] Result := aArg1 + aArg2; ... The implicit does not have those. Actually I checked that a bit deeper. The comment occur in (and only in) the first specialization (implicit or explicit). All other specialization are without comments for the source.

I had that issue in a different context already and it's related to the assembler writer and when it decides a line should be written again (cause at the basic level it would write the line comment for each instruction of that line and thus it needs to be reset correctly, but across functions that should be reset completely of course). I already have a fix for that, but as it's buried in a different branch it will be a little while longer. ;)

Regards,
Sven

Sven Barth
to Rainer Stratmann: Am Freitag, 22. April 2022, 19:53:34 CEST schrieben Sie: pascal: Of course 'you' do. When…
Rainer Stratmann wrote 11 lines
Am Freitag, 22. April 2022, 19:53:34 CEST schrieben Sie: > Am 22.04.2022 um 15:48 schrieb Rainer Stratmann via fpc-pascal: >> Am Mittwoch, 20. April 2022, 19:15:15 CEST schrieb Sven Barth via fpc- pascal: > We don't deal in percentages, however it reduces the amount of typing > required to write code with a lot of specializations (in theory an IDE > like Lazarus *could* help here as well however). Of course 'you' do. When I asked for a simple feature years ago it was refused. And there were several explanations why this feature is not necessary.

I quickly checked the archives and found two suggestions by you:

1) "Name of a var" from 2011 (
https://lists.freepascal.org/pipermail/fpc-pascal/2011-November/031256.html )

2) "Declaration of arrays" from 2013 (
https://lists.freepascal.org/pipermail/fpc-pascal/2013-June/038496.html )

For 1) I'm still of the opinion that it would be useful especially considering that we now have {$I %CURRENTROUTINE%} as well.

For 2) I'm also of the opinion of the others: this is unneeded syntactic sugar. There is already a way to declare arrays with a specific size and for a language it's in nearly all cases not good to provide multiple ways to achieve the same.

As I've written elsewhere: implicit function specializations as a feature might not have happened if Delphi did not support them as well, cause like 2) they are essentially syntactic sugar as well. Though contrary to your idea they don't provide an alternative way to specialize functions, but really provide a way to explicitely declare less. It's a real subtle difference between the two situations, but it's there.
The main argument however is Delphi compatibility. The amount of users that want features from Delphi is much higher than the amount of users that require features from MikroPascal and thus features that are syntactic sugar will see the light of day much easier.

Regards,
Sven

Hairy Pixels
to Sven Barth
On Apr 23, 2022, at 3:40 PM, Sven Barth via fpc-pascal wrote: For 2) I'm also of the opinion of the others: this is unneeded syntactic sugar. There is already a way to declare arrays with a specific size and for a language it's in nearly all cases not good to provide multiple ways to achieve the same.

Getting off topic but personally I think the idea of a range bound for arrays has been proven to be a bad idea from the original Pascal spec. Originally when learning pascal I think I did 1 indexed arrays but eventually realized it’s non-standard across other languages and doesn’t really provide any use. In 99.99% of cases I always do 0…n so it’s just wasting time at this point. In fact I would be really hard pressed to think of a time I did something besides 0…n…

As I've written elsewhere: implicit function specializations as a feature might not have happened if Delphi did not support them as well, cause like 2) they are essentially syntactic sugar as well.

The two languages I use commonly these days are Swift and C#, both of which do implicit function specialization by default. After you use a generic function a couple times it becomes apparent the compiler could infer the types and it’s less code to write so it’s natural that any language that has generic functions would support this feature.

Regards,
Ryan Joseph

Marco van de Voort
to Hairy Pixels
Hairy Pixels wrote
> For 2) I'm also of the opinion of the others: this is unneeded syntactic sugar. There is already a way to declare arrays with a specific size and for a language it's in nearly all cases not good to provide multiple ways to achieve the same. Getting off topic but personally I think the idea of a range bound for arrays has been proven to be a bad idea from the original Pascal spec. Originally when learning pascal I think I did 1 indexed arrays but eventually realized it’s non-standard across other languages and doesn’t really provide any use. In 99.99% of cases I always do 0…n so it’s just wasting time at this point. In fact I would be really hard pressed to think of a time I did something besides 0…n…

0..n-1  I might hope.

Btw since you are afaik an Apple user, did you actually use conformant arrays, or do you base this on UCSD/Borland dialects only?

> As I've written elsewhere: implicit function specializations as a feature might not have happened if Delphi did not support them as well, cause like 2) they are essentially syntactic sugar as well. The two languages I use commonly these days are Swift and C#, both of which do implicit function specialization by default. After you use a generic function a couple times it becomes apparent the compiler could infer the types and it’s less code to write so it’s natural that any language that has generic functions would support this feature.

IMHO It is like with all shorthands, if an addition causes pitfalls, debugging those is usually worse then they ever save.

I've no idea about how that is with this feature (and then most notably the integration in the Pascal language, since pitfalls are often due to combinations of features)

Thomas Kurz

Dear all,

I'm not sure whether or not this is an issue with fpdebug, so I'd like to describe it here first:

I have a main program (Win32 GUI running on Win 8.1) which loads and unloads a DLL dynamically. I know that fpdebug is currently not able to debug DLLs, but imho I don't do so, but the issue might be related.

The program runs fine when being run without debugger or when using GDB. However, a crash occurs when fpdebug is active and the call to "FreeLibrary" is made. The error is:

Project raised exception class 'External: Unknown exception code $E0465043'. At address 76EB56E8.

The DLL is my own and I tried to debug the Finalization section with GDB, but I cannot find an issue there. So imho unloading the DLL should not cause any trouble. As I said, I'm not trying to actually step into the DLL; I'm only using fpdebug in the main program and trying to step over the "FreeLibrary" line.

Kind regards,
Thomas

Martin Frb
to Thomas Kurz: I'm not sure whether or not this is an issue with fpdebug, so I'd like to describe it…

Lazarus 2.2 or 2.3 ?
In 2.3 some work has been started to debug into dll...

Is there a way to reproduce this?
Also try:
lazarus.exe  --debug-log=c:\logfile.txt
--debug-enable=DBG_EVENTS,DBG_VERBOSE,DBG_WARNINGS,DBG_ERRORS,DBG_VERBOSE_BRKPOINT,DBG_STATE,DBG_COMMAND_ECHO,DBG_DATA_MONITORS,DBG_LOCATION_INFO,DBG_BREAKPOINTS,DBG_FPDEBUG_VERBOSE,FPDBG_BREAKPOINT_ERRORS,FPDBG_BREAKPOINTS,FPDBG_COMMANDS,FPDBG_THREADS,FPDBG_QUEUE,FPDEBUG_THREAD_CHECK,FPDBG_WINDOWS

I am away for some days, so replies may take some time.

Thomas Kurz wrote 18 lines
Dear all, I'm not sure whether or not this is an issue with fpdebug, so I'd like to describe it here first: I have a main program (Win32 GUI running on Win 8.1) which loads and unloads a DLL dynamically. I know that fpdebug is currently not able to debug DLLs, but imho I don't do so, but the issue might be related. The program runs fine when being run without debugger or when using GDB. However, a crash occurs when fpdebug is active and the call to "FreeLibrary" is made. The error is: Project raised exception class 'External: Unknown exception code $E0465043'. At address 76EB56E8. The DLL is my own and I tried to debug the Finalization section with GDB, but I cannot find an issue there. So imho unloading the DLL should not cause any trouble. As I said, I'm not trying to actually step into the DLL; I'm only using fpdebug in the main program and trying to step over the "FreeLibrary" line. Kind regards, Thomas _______________________________________________ fpc-pascal maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Marco van de Voort
to Hairy Pixels: I started with THINK Pascal on classic Mac OS but I never heard of conformant arrays.…

Op 24/04/2022 om 04:37 schreef Hairy Pixels via fpc-pascal:

> On Apr 23, 2022, at 10:30 PM, Marco van de Voort via fpc-pascal wrote: > > Btw since you are afaik an Apple user, did you actually use conformant arrays, or do you base this on UCSD/Borland dialects only? I started with THINK Pascal on classic Mac OS but I never heard of conformant arrays. What are they?

It's a ISO pascal feature that is a kind of open array construct where you can pass non zero based arrays too. The limitation that dynamic/open arrays are always zero based is a Delphi simplification.

It is more or less meant that if to judge the non zero array bounderies features, you need to know the whole story.

Hairy Pixels
to Hairy Pixels: Getting off topic but personally I think the idea of a range bound for arrays has been…
On Apr 23, 2022, at 9:09 PM, Hairy Pixels wrote: The two languages I use commonly these days are Swift and C#, both of which do implicit function specialization by default. After you use a generic function a couple times it becomes apparent the compiler could infer the types and it’s less code to write so it’s natural that any language that has generic functions would support this feature.

I was thinking about this more and I actually feel like we got this backwards in Pascal and the default should be implicit specialization with manual specialization being a fallback for more challenging cases.

If you have a function:

	generic function Add<T>(left, right: T): T;

And the programmer intends to call it like:

	Add(1,1);

or

	var
	  a, b: Integer;
	begin
	  Add(a, b);

Why should the programmer need to tell the compiler it’s dealing with Integer when it’s so obvious from the types being used? All the programmer should know is that there is type “T” and any type will work (I know, we don’t have type constraints for built-in types and yes, we should implement that).

For the case of types I understand we must specialize because we’re creating a new type each time and there is nothing to infer from the context, but for functions it’s backwards, that is we know the types being used, we just need to choose the right function based on those types.

If it’s ever allowed to do generic type helpers (I would like do this also) I think the same idea applies. Manually specializing the type doesn’t provide any better control then allowing the compiler to specialize based on the type being extended.

type
  generic TArrayHelper<T> = type helper for array of T
    procedure Add(value: T);
  end;
var
  a: array of integer;
begin
  a.Add(1);  // specialize TArrayHelper<Integer> since the compiler clearly knows the type used is Integer.
end.

Regards,
Ryan Joseph

Martin Frb
to Hairy Pixels
Hairy Pixels wrote 8 lines
generic function Add<T>(left, right: T): T; And the programmer intends to call it like: Add(1,1); Why should the programmer need to tell the compiler it’s dealing with Integer when it’s so obvious from the types being used?

Actually, it's dealing with SmallInt (or ShortInt). And if the programmer does not know that, then it might be an issue...

Imagine the generic code (something more complex than "Add") would somehow do something that differs for SmallInt and Integer. Like using "SizeOf(T)".

Sure it can be solved by explicit specialization "Add<Integer>(1,1)". But to solve it that way, the programmer needs to know... And he might not...

Hairy Pixels
to Martin Frb
Show quoted text 5 lines
On Apr 25, 2022, at 2:39 PM, Martin Frb via fpc-pascal wrote: Actually, it's dealing with SmallInt (or ShortInt). And if the programmer does not know that, then it might be an issue... Imagine the generic code (something more complex than "Add") would somehow do something that differs for SmallInt and Integer. Like using "SizeOf(T)".

This is almost a case that requires better type restrictions. If your generic function relies specifically on some particular type information this is almost not really even a good candidate to be , Ryan Joseph

Sven Barth
to Hairy Pixels

Hairy Pixels via fpc-pascal schrieb am
Mo., 25. Apr. 2022, 10:58:

Show quoted text 16 lines
> On Apr 25, 2022, at 2:39 PM, Martin Frb via fpc-pascal < > wrote: > > Actually, it's dealing with SmallInt (or ShortInt). And if the programmer does not know that, then it might be an issue... > > Imagine the generic code (something more complex than "Add") would somehow do something that differs for SmallInt and Integer. Like using "SizeOf(T)". This is almost a case that requires better type restrictions. If your generic function relies specifically on some particular type information this is almost not really even a good candidate to be generic at all.

Then you don't think creative enough. Think about (de)serialization code for binary data which needs to use the correct size. If done correctly this can very well be handled with a generic.

Regards,
Sven

Hairy Pixels
to Sven Barth
On Apr 25, 2022, at 4:11 PM, Sven Barth wrote: Then you don't think creative enough. Think about (de)serialization code for binary data which needs to use the correct size. If done correctly this can very well be handled with a generic.

Oh yes that’s right. I guess I would need some real examples of a function that used type information and could suffer a failure by the compiler assuming the wrong type from a constant like “1”. If you were deserializing you would probably be passing in a very particular type and not some ambiguous thing like a number literal.

Having said that there should be type restrictions for Integer, Real, Boolean and String (maybe Char) etc… Right now only class, record and interface are supported.

Regards,
Ryan Joseph

Marco van de Voort
to Sven Barth: Hairy Pixels via fpc-pascal schrieb am Mo., 25. Apr. 2022, 10:58: Then you don't think…
Sven Barth wrote
Then you don't think creative enough. Think about (de)serialization code for binary data which needs to use the correct size. If done correctly this can very well be handled with a generic.

Actually that's what I immediate thought of too, but nearly all also have string as type. Can this be solved by having a specific overloaded version for string ?

I'm thinking of things like TStream helpers in streamex.

Mattias Gaertner
to Hairy Pixels: I was thinking about this more and I actually feel like we got this backwards in Pascal…
Show quoted text 6 lines
[...] For the case of types I understand we must specialize because we’re creating a new type each time and there is nothing to infer from the context, but for functions it’s backwards, that is we know the types being used, we just need to choose the right function based on those types.

IMO the important difference is that alias types already safes most inline specializations.
There is no alias for generic functions, which is why implicit function specialization is so handy.

Show quoted text 11 lines
[...] type generic TArrayHelper<T> = type helper for array of T procedure Add(value: T); end; var a: array of integer; begin a.Add(1); // specialize TArrayHelper<Integer> since the compiler clearly knows the type used is Integer. end.

IMO that is only useful with modeswitch multihelpers.

All these modeswitches makes it harder to learn Pascal.

Mattias

Sven Barth
to Mattias Gaertner

Mattias Gaertner via fpc-pascal schrieb
am Mo., 25. Apr. 2022, 13:50:

Show quoted text 13 lines
>[...] > type > generic TArrayHelper<T> = type helper for array of T > procedure Add(value: T); > end; > > var > a: array of integer; > begin > a.Add(1); // specialize TArrayHelper<Integer> since the compiler > clearly knows the type used is Integer. end.

For some reason I don't get all mails by Ryan, so I reply here:

Helpers currently can't be generic and even if they could be (which I do plan to add) it would require an explicit specialization of the helper to be used because the compiler will not be tasked with searching for potential generic helpers and then implicitly specializing a whole helper type with all its methods to maybe use it for an implicit specialization (and it needs to specialize the helper first to decide whether it even remotely contains suitable methods).

Regards,
Sven

Hairy Pixels
to Sven Barth
Show quoted text 6 lines
On Apr 25, 2022, at 7:14 PM, Sven Barth via fpc-pascal wrote: For some reason I don't get all mails by Ryan, so I reply here: Helpers currently can't be generic and even if they could be (which I do plan to add) it would require an explicit specialization of the helper to be used because the compiler will not be tasked with searching for potential generic helpers and then implicitly specializing a whole helper type with all its methods to maybe use it for an implicit specialization (and it needs to specialize the helper first to decide whether it even remotely contains suitable methods).

You could do a search for method names and parameters to see if a generic helper was a candidate before specialization. That would reduce the possible space for needless specializations right? For non-object types this would be even less intrusive since it would only be competing with other helper methods.

Regards,
Ryan Joseph

Hairy Pixels
to Hairy Pixels
Show quoted text 8 lines
On Apr 25, 2022, at 8:27 PM, Hairy Pixels wrote: > For some reason I don't get all mails by Ryan, so I reply here: > > Helpers currently can't be generic and even if they could be (which I do plan to add) it would require an explicit specialization of the helper to be used because the compiler will not be tasked with searching for potential generic helpers and then implicitly specializing a whole helper type with all its methods to maybe use it for an implicit specialization (and it needs to specialize the helper first to decide whether it even remotely contains suitable methods). > You could do a search for method names and parameters to see if a generic helper was a candidate before specialization. That would reduce the possible space for needless specializations right? For non-object types this would be even less intrusive since it would only be competing with other helper methods.

Note: CC’ing Sven in case he’s not getting these.

First off see my reply above about implicit. Question is why can’t you filter out possible helpers by doing a pre-pass check for matching names? It’s certainly going to add some compile time overhead but it can be put behind a mode switch in the case it’s worthwhile (the compile time may be negligible in many cases). There’s also some optimizations I could think of so I wouldn’t give up on implicit specialization just yet.

I had a look at some old notes I had on the idea of generic helpers since we talked about this some years back (for generic arrays) and here's what I found.

2) Right now helpers only support single types but they would need to be extended to support “array of T”, “set of T”,which by, why aren’t those supported as it is? I can’t seem to think of any reason they should be disallowed. The alternative is using an auxiliary generic type which kind of confounds things because then you’re extending a new type instead of the underlying array of set type.

3) If there was a generic helper for a generic class it may be too verbose to use the normal inline specialization syntax ie:

generic THelper<T> = class helper for specialize TMyClass<T>

So I wonder if the shorthand:

generic THelper<T> = class helper for TMyClass

Could be used, providing the type to be extended has a matching generic parameter list. At the very least the “specialize” sounds wrong when reading it.

Regards,
Ryan Joseph

Mattias Gaertner
to Hairy Pixels
[...] So I wonder if the shorthand: generic THelper<T> = class helper for TMyClass

AFAIK it is planned for mode objfpc to support distinguishing types via template count as in mode delphi:

type
  TMyClass = class
  end;
  generic TMyClass<T> = class
  end;
  generic TMyClass<S,T> = class
  end;

So you would need something similar for helpers:

  THelper = class helper for TMyClass
  end;
  generic THelper<T> = class helper for specialize TMyClass<T>
  end;
  generic THelper<S,T> = class helper for specialize TMyClass<S,T>
  end;

Mattias

Hairy Pixels
to Mattias Gaertner
Show quoted text 21 lines
On Apr 30, 2022, at 7:02 PM, Mattias Gaertner via fpc-pascal wrote: AFAIK it is planned for mode objfpc to support distinguishing types via template count as in mode delphi: type TMyClass = class end; generic TMyClass<T> = class end; generic TMyClass<S,T> = class end; So you would need something similar for helpers: THelper = class helper for TMyClass end; generic THelper<T> = class helper for specialize TMyClass<T> end; generic THelper<S,T> = class helper for specialize TMyClass<S,T> end;

Now that would be a duplicate identifier error but that may be changed for Delphi compatibility? That indeed would break any possible shorthands. Maybe at least the specialize could be omitted because of the grammar which is so annoying. :)

To the extent I have a todo list I’ve had plans to make a mode switch for “generic keywords” so you could disable them in Object Pascal mode (Svens idea). That would fix the specialize problem but honestly I kind of like the generic keyword as it’s a prefix and makes the type more clear. It’s the specialize keyword I have a problem with.

Regards,
Ryan Joseph

Hairy Pixels
to Hairy Pixels: Note: CC’ing Sven in case he’s not getting these. First off see my reply above about…
On Apr 30, 2022, at 6:17 PM, Hairy Pixels wrote: 2) Right now helpers only support single types but they would need to be extended to support “array of T”, “set of T”,which by, why aren’t those supported as it is? I can’t seem to think of any reason they should be disallowed. The alternative is using an auxiliary generic type which kind of confounds things because then you’re extending a new type instead of the underlying array of set type.

Looking at this problem this morning and I wanted to give an example of what I was talking about.

type
  generic TArray<T> = array of T;
  generic TMyHelper<T> = type helper for specialize TArray<T>
  end;
  TIntHelper = specialize TMyHelper<Integer>;

The problem is TIntHelper is going to extend the type TArray<Integer> instead of “array of Integer” (not to mention the boiler plate) so extra steps would have to be taking to make sure “array of T” type can find the helpers for TArray<T>.

Not sure what the best solution is but here are the ones I can think of:

1) allow the helpers to use “array/set of T” syntax.
2) register helpers for both types.
3) perform an extra step to search helpers for “array of T” or “set of T” types.

Regards,
Ryan Joseph

Hairy Pixels
to Hairy Pixels
Show quoted text 5 lines
On May 1, 2022, at 10:36 AM, Hairy Pixels wrote: Not sure what the best solution is but here are the ones I can think of: 1) allow the helpers to use “array/set of T” syntax.

Sorry for the noise, one last point on this. So it looks like type helpers are already so strictly typed that a type alias for a dynamic array is not compatible with the anonymous dynamic array type (see below). Can this restriction be lifted or is it intentional in the design? I would think that TIntArray is just an alias and thus should be functionally the same as “array of integer”.

type
  TIntArray = array of integer;
  TMyArrayHelper = type helper for TIntArray
    procedure DoThis;
  end;
  procedure TMyArrayHelper.DoThis;
  begin
    writeln(Length(self));
  end;
var
  a: array of integer;
begin
  a.DoThis; // Illegal qualifier:
end.

Regards,
Ryan Joseph