Recent

Author Topic: Generic functions revisited  (Read 3056 times)

Bogen85

  • Hero Member
  • *****
  • Posts: 703
Generic functions revisited
« on: December 19, 2022, 05:14:42 pm »
I stumbled upon this: https://forum.lazarus.freepascal.org/index.php?topic=33327.0
That topic is from July of 2016.

From that topic I see that this is possible:

Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2.  
  3. generic function f<T>(a: T): T;
  4. begin
  5.   Result := a + a;
  6. end;
  7.  
  8. begin
  9.   writeln(specialize f<Integer>(5));
  10.   writeln(specialize f<String>('test'));
  11. end.
  12.  

But how can one pre-declare those without having to use specialize when they are called?

I don't know off hand how one could directly declare/defined specialize generic functions, but I know one can indirectly declare/define them as public static class methods of an empty record.

Here is an example of what that would look like with static class methods of an empty record:

Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2. {$H+}
  3. {$modeswitch advancedrecords}
  4.  
  5. program example1;
  6.  
  7. uses
  8.   sysutils;
  9.  
  10. type
  11.   generic TSomething<T> = record
  12.   public
  13.     class function method1(constref item: T): T; static;
  14.     class procedure method2(constref item: T); static;
  15.   end;
  16.  
  17.   TSomeInt = specialize TSomething<integer>;
  18.   TSomeString = specialize TSomething<string>;
  19.  
  20. class function TSomething.method1(constref item: T): T;
  21.   begin
  22.     result := item + item;
  23.   end;
  24.  
  25. class procedure TSomething.method2(constref item: T);
  26.   var
  27.     tstr, val: string;
  28.   begin
  29.     writestr(tstr, GetTypeKind(T));
  30.     writestr(val, item);
  31.     writeln(format('item type: %s item: %s', [tstr, val]));
  32.   end;
  33.  
  34. begin
  35.   writeln(TSomeInt.method1(5));
  36.   writeln(TSomeString.method1('five'));
  37.   TSomeInt.method2(10);
  38.   TSomeString.method2('ten');
  39. end.
  40.  

A bit verbose, but it does work:

Code: Text  [Select][+][-]
  1. $ fpc example1.pas
  2. Free Pascal Compiler version 3.3.1 [2022/12/19] for x86_64
  3. Copyright (c) 1993-2022 by Florian Klaempfl and others
  4. Target OS: Linux for x86-64
  5. Compiling example1.pas
  6. Linking example1
  7. 39 lines compiled, 0.2 sec, 375424 bytes code, 166488 bytes data
  8.  
  9. $ ./example1
  10. 10
  11. fivefive
  12. item type: tkInteger item: 10
  13. item type: tkAString item: ten
  14.  

So back to my question, how can one pre-declare specialized generic functions without having to use specialize when they are called?
« Last Edit: December 19, 2022, 05:31:20 pm by Bogen85 »

Thaddy

  • Hero Member
  • *****
  • Posts: 18970
  • Glad to be alive.
Re: Generic functions revisited
« Reply #1 on: December 19, 2022, 06:06:28 pm »
Use {$mode delphi} instead may work, but it would be Delphi incompatible, because Delphi does not support that at all.
Not only do the syntax between objfpc and delphi differ (specialize), objfpc can do way more.
What is your problem with specialize? It gives you a wider range of opportunities...
« Last Edit: December 19, 2022, 06:13:40 pm by Thaddy »
Recovered from removal of tumor in tongue following tongue reconstruction with a part from my leg.

Bogen85

  • Hero Member
  • *****
  • Posts: 703
Re: Generic functions revisited
« Reply #2 on: December 19, 2022, 06:19:41 pm »
Use {$mode delphi} instead may work, but it would be Delphi incompatible, because Delphi does not support that at all.
Not only do the syntax between objfpc and delphi differ (specialize), objfpc can do way more.
What is your problem with specialize? It gives you a wider range of opportunities...

specialize is fine I guess if I only use it once. Yeah, it is more obvious which one you are getting.
If more than than once might be more verbose (or less..., depends) than my empty record example.

I guess one would assume that specialize used more than once for the same type would result in only one instance for that type.

With my empty record example I know how to pre specialize once and use many times.
The question was can I pre  specialize once and use many times when just using a generic function/procedure that has not been wrapped as a static class member in an empty record.

But, that could just be eye-candy as you often point out, and may not be the best way to go about it...
« Last Edit: December 19, 2022, 06:32:30 pm by Bogen85 »

Peter H

  • Sr. Member
  • ****
  • Posts: 272
Re: Generic functions revisited
« Reply #3 on: December 19, 2022, 06:38:20 pm »
This seems to work:

Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2. {$macro on}
  3.  
  4. generic function gf<T>(a: T): T;
  5. begin
  6.   Result := a + a;
  7. end;
  8. {$define f := specialize gf}
  9. begin
  10.   writeln(f<Integer>(5));
  11.   writeln(f<String>('test'));
  12. readln;
  13. end.
  14.  

Bogen85

  • Hero Member
  • *****
  • Posts: 703
Re: Generic functions revisited
« Reply #4 on: December 19, 2022, 06:50:20 pm »
This seems to work:

Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2. {$macro on}
  3.  
  4. generic function gf<T>(a: T): T;
  5. begin
  6.   Result := a + a;
  7. end;
  8. {$define f := specialize gf}
  9. begin
  10.   writeln(f<Integer>(5));
  11.   writeln(f<String>('test'));
  12. readln;
  13. end.
  14.  

Yes, as does this:
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2. {$H+}
  3. {$macro on}
  4.  
  5. generic function gf<T>(a: T): T;
  6. begin
  7.   Result := a + a;
  8. end;
  9. {$define fint := specialize gf<Integer>}
  10. {$define fstr := specialize gf<String>}
  11. begin
  12.   writeln(fint(5));
  13.   writeln(fstr('test'));
  14. end.
  15.  

Which is guess would be the answer I was looking for...

But I was hoping to not resort to using macros, as I did not have to use macros in my example using static class members with an empty (and unused) record type.

Peter H

  • Sr. Member
  • ****
  • Posts: 272
Re: Generic functions revisited
« Reply #5 on: December 19, 2022, 07:00:45 pm »
A wrapper function which is inlined could possibly work.
This is probably a better solution.

Bogen85

  • Hero Member
  • *****
  • Posts: 703
Re: Generic functions revisited
« Reply #6 on: December 19, 2022, 07:11:53 pm »
A wrapper function which is inlined could possibly work.
This is probably a better solution.

Yeah, which I can do when wrapping these functions in an empty record as static class methods (adding inline in addition to static).

I guess wrapping these in a namespace (empty record that is never allocated) gives me the the most options...

Thanks!

PascalDragon

  • Hero Member
  • *****
  • Posts: 6381
  • Compiler Developer
Re: Generic functions revisited
« Reply #7 on: December 19, 2022, 10:08:41 pm »
But how can one pre-declare those without having to use specialize when they are called?

You can't. But using it multiple times with the same types will result in the compiler specializing it only once (at least inside the same unit; cross unit support for this is yet to be implemented).

In 3.3.1 you can also use the modeswitch ImplicitFunctionSpecialization which allows you to do the following (though depending on the parameters you might need to pay attention to the types you pass especially with constant values):

Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2. {$modeswitch implicitfunctionspecialization}
  3.  
  4. generic function f<T>(a: T): T;
  5. begin
  6.   Result := a + a;
  7. end;
  8.  
  9. begin
  10.   writeln(f(5));
  11.   writeln(f('test'));
  12. end.

Independant of the mode neither the specialize nor the type parameters are required.

Bogen85

  • Hero Member
  • *****
  • Posts: 703
Re: Generic functions revisited
« Reply #8 on: December 20, 2022, 02:09:36 am »
In 3.3.1 you can also use the modeswitch ImplicitFunctionSpecialization which allows you to do the following (though depending on the parameters you might need to pay attention to the types you pass especially with constant values):
...
Independant of the mode neither the specialize nor the type parameters are required.

I see...
-MimplicitFunctionSpecialization

Alright, I just now tried it, and replaced my generic struct static class method mechanism to use that instead, and is working as desired.
(Slightly different syntax, but less verbose overall).

I believe I'll use that.

Thanks!


Peter H

  • Sr. Member
  • ****
  • Posts: 272
Re: Generic functions revisited
« Reply #9 on: December 20, 2022, 10:50:20 am »
Good to know thanks!

Nevertheless I tried this, it is more writing, but also has advantages.

It gives some control over the arguments, the compiler will barf if they dont match
The wrapper functions can be used as debug hooks, if "inline" is removed.
It should work with older compilers.

Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2. {$H+}
  3. {$macro on}
  4.  
  5. generic function f<T>(a: T): T;
  6. begin
  7.   Result := a + a;
  8. end;
  9.  
  10. // Wrapper functions should be always inlined by the compiler
  11. // because they contain almost no code
  12. function f(i:integer):integer;  inline;
  13. begin
  14.   result := specialize f<Integer>(i);
  15. end;
  16.  
  17. function f(i:string):string;  inline;
  18. begin
  19.   result := specialize f<String>(i);
  20. end;
  21.  
  22. var
  23.    d:double=2.0;
  24. begin
  25.   writeln(f(5));
  26.   writeln(f('test'));
  27.   // writeln(f(d)); //Will not compile, because wrapper function is missing
  28.                     //This is intended behaviour
  29.   readln;
  30. end.
  31.  

Thaddy

  • Hero Member
  • *****
  • Posts: 18970
  • Glad to be alive.
Re: Generic functions revisited
« Reply #10 on: December 20, 2022, 12:34:46 pm »
No it will not work with older compilers.
Recovered from removal of tumor in tongue following tongue reconstruction with a part from my leg.

Peter H

  • Sr. Member
  • ****
  • Posts: 272
Re: Generic functions revisited
« Reply #11 on: December 20, 2022, 01:08:15 pm »
I meant older than trunk. It works with stable.

Thaddy

  • Hero Member
  • *****
  • Posts: 18970
  • Glad to be alive.
Re: Generic functions revisited
« Reply #12 on: December 20, 2022, 04:14:22 pm »
That is current, not old. Trunk is not current but future (and will stay so for some time to come)
Recovered from removal of tumor in tongue following tongue reconstruction with a part from my leg.

PascalDragon

  • Hero Member
  • *****
  • Posts: 6381
  • Compiler Developer
Re: Generic functions revisited
« Reply #13 on: December 20, 2022, 10:07:44 pm »
I see...
-MimplicitFunctionSpecialization

Please note that modeswitches passed by the command line will be reset upon a $mode directive.

Bogen85

  • Hero Member
  • *****
  • Posts: 703
Re: Generic functions revisited
« Reply #14 on: December 20, 2022, 10:17:58 pm »
I see...
-MimplicitFunctionSpecialization

Please note that modeswitches passed by the command line will be reset upon a $mode directive.

I usually just set these in my project and not in the source files themselves, so unless I'm making examples I just have this:

Code: [Select]
-Mobjfpc -Sh -vewnh -Sewnh -gh -gl -gw3 -Xg -Xi -Sm -Sg -Os -Si
-MadvancedRecords -MfunctionReferences -ManonymousFunctions
-MimplicitFunctionSpecialization -OoUnusedPara -McVar -OoPeepHole
-OoTailRec -OoRemoveEmptyProcs -OoConstProp -OoDFA -OoCSE
-OoDeadStore -OoDeadValues

Does the order of those matter? (as I set the mode earlier, or does that only apply in in the source file?)

 

TinyPortal © 2005-2018