Lazarus

Announcements => Free Pascal => Topic started by: PascalDragon on May 26, 2022, 09:47:14 pm

Title: Feature announcement: Function References and Anonymous Functions
Post by: PascalDragon on May 26, 2022, 09:47:14 pm
Dear Free Pascal Community,

The Free Pascal Developer team is pleased to finally announce the addition of a long awaited feature, though to be precise it's two different, but very much related features: Function References and Anonymous Functions. These two features can be used independantly of each other, but their greatest power they unfold when used together.

These features are based on the work by Blaise.ru, so thank you very much and I hope you're doing well considering the current situation.

In the following we'll highlight both features separately and then we'll take a look at using them together.

Function References

Function References (also applicable names are Procedure References and Routine References, in the following only Function References will be used) are types that can take a function (or procedure or routine), method, function variable (or procedure variable or routine variable), method variable, nested function (or nested procedure or nested routine) or an anonymous function (or anonymous procedure or anonymous routine) as a value. The function reference can then be used to call the provided function just like other similar routine pointer types. In contrast to these other types nearly all function-like constructs can be assigned to it (the only exception are nested function variables (or nested procedure variables or nested routine variables), more about that later on) and then used or stored.

Function references are enabled with the modeswitch FUNCTIONREFERENCES (the following examples will assume that this modeswitch is provided).

A function reference is declared as follows:

REFERENCE TO FUNCTION|PROCEDURE [(argumentlist)][: resulttype;] [directives;]

Examples:

Code: Pascal  [Select][+][-]
  1. type
  2.   TProcLongInt = reference to procedure(aArg: LongInt); stdcall;
  3.   TFuncTObject = reference to function(aArg: TObject): TObject;

Like other function pointer types function references can also be declared as generic:

Code: Pascal  [Select][+][-]
  1. type
  2.   generic TGenericProc<T> = reference to procedure(aArg: T);

As you can see, once function references are enabled you can't use the identifier "REFERENCE" as part of an alias declaration without using "&":

Code: Pascal  [Select][+][-]
  1. type
  2.   someref = reference; // will fail
  3.   someref = &reference; // correct fix
  4.  
  5. var
  6.   somevar: reference; // will fail
  7.   somevar: &reference; // correct fix

A function reference variable can then be called like any other function pointer type:

Code: Pascal  [Select][+][-]
  1. var
  2.   p: TProcLongInt;
  3. begin
  4.   p := @SomeLongIntProc;
  5.   p(42);
  6. end.

If a function reference has no parameters then you need to use "()" nevertheless in the FPC/ObjFPC modes like for other function pointer types:

Code: Pascal  [Select][+][-]
  1. type
  2.   TProc = reference to procedure;
  3. var
  4.   p: TProc;
  5. begin
  6.   p := @SomeProcedure;
  7.   p(); // required
  8.   p; // this can be used e.g. in mode Delphi
  9. end.

Like other function pointer types they can also be declared anonymously as part of a variable, field declaration (but not as part of a paramater declaration):

Code: Pascal  [Select][+][-]
  1. var
  2.   f: reference to function: LongInt;
  3.  
  4. type
  5.   TTest = class
  6.     f: reference to procedure;
  7.   end;

They get their great power from a point that is for once not considered an implementation detail: function references are in fact internally declared as reference counted interfaces with a single Invoke() method of the provided signature. So the above examples are in fact declared like this:

Code: Pascal  [Select][+][-]
  1. type
  2.   TProcLongInt = interface(IInterface)
  3.     procedure Invoke(aArg: LongInt); stdcall; overload;
  4.   end;
  5.  
  6.   TFuncTObject = interface(IInterface)
  7.     procedure Invoke(aArg: TObject): TObject; overload;
  8.   end;
  9.  
  10.   generic TGenericProc<T> = interface(IInterface)
  11.     procedure Invoke(aArg: T); overload;
  12.   end;

This has a few implications:

Especially the last two points are important.

That the interface can be implemented means that much more functionality and state can be added to a function reference:

Code: Pascal  [Select][+][-]
  1. type
  2.   TFunc = reference to function: LongInt;
  3.  
  4.   TSomeImpl = class(TInterfacedObject, TFunc)
  5.     f: LongInt;
  6.     function Invoke: LongInt;
  7.   end;
  8.  
  9. function TSomeImpl.Invoke: LongInt;
  10. begin
  11.   Result := f;
  12. end;
  13.  
  14. var
  15.   t: TSomeImpl;
  16.   f: TFunc;  
  17. begin
  18.   t := TSomeImpl.Create;
  19.   f := t;
  20.   Writeln(f()); // will write 0
  21.   t.f := 42;
  22.   Writeln(f()); // will write 42
  23.   f := Nil; // the usual warnings about mixing classes and interface apply!
  24. end.

As function references don't have valid GUIDs you can't however use QueryInterface() or the as-operator to retrieve it. Using the low level interface related functions of TObject however will work.

An interface that inherits from a function reference is still considered invokable by the compiler, so it can still be used like an ordinary function reference could, but you can also add additional methods including overloads for Invoke itself:

Code: Pascal  [Select][+][-]
  1. type
  2.   TTest = reference to procedure(aArg: TObject);
  3.  
  4.   TTestEx = interface(TTest)
  5.     function Invoke: TObject; overload;
  6.   end;
  7.  
  8. var
  9.   f: TTestEx;
  10.   o: TObject;
  11. begin
  12.   f := TSomeImplEx.Create;
  13.   o := f();
  14.   f(o);
  15. end.

This is for example described by Stefan Glienke on his blog (https://delphisorcery.blogspot.com/2015/06/anonymous-method-overloading.html). His linked example won't work as-is however due to missing functionality in Rtti.TValue.

As mentioned initially you can assign a nested function to a function reference, but not a nested function variable. There is no real technical reason for this, but it's instead a design choice based on how function references are assumed to behave: they are assumed to be valid beyond their scope (this will become clearer when combined with anonymous functions in the third part), so they can for example be returned from a function or stored in some class instance and can still be considered valid. However a nested function variable is no longer useable once the function frame it was retrieved has been left (for a nested function the compiler can safely convert it in a way that this is no problem, but for a nested function variable it simply can't).
One could argue that the same is true for method pointers and method variables as they aren't callable anymore once their class instance is freed however these are much more common in the Object Pascal world while nested function variables are very seldom used, thus the dangers of the former are much more apparent than the dangers of the later.
For this reason assigning nested function variables to function references is prohibited.

Anonymous Functions

Anonymous Functions (or Anonymous Procedures or Anonymous Routines, in the following simply Anonymous Functions) are routines that have no name associated with them and are declared in the middle of a code block (for example on the right side of an expression or as a parameter for a function call). However they can just as well be called directly like a nested function (or nested procedure or nested routine) would.

Anonymous functions are enabled with the modeswitch ANONYMOUSFUNCTIONS (the following examples will assume that this modeswitch is provided).

An anonymous function is declared as follows:

FUNCTION|PROCEDURE [(argumentlist)][[resultname]: resulttype;] [directives;]
[[VAR|TYPE|CONST section]|[nested routine]]*
BEGIN
[STATEMENTS]
END


As can be seen an anonymous function looks like a regular function (or procedure or routine) with the most important differences being that it does not have a name and that it isn't terminated by a semicolon (because it's essentially an expression). Because it doesn't have a name for modes that don't have the implicit RESULTvariable it's allowed to explicitely name the result variable (even in modes that do have the RESULT variable) like is the case with operator overloads.

It's possible to directly call an anonymous function in which case it essentially behaves like a nested function.

Like nested functions anonymous functions have access to the symbols (variables, functions, etc.) of the surrounding scope including Self if the surrounding scope is a method. Accessing such a symbol is named “capturing” and is one of the core concepts of anonymous functions.

Their main use however is when assigning them to one of the various function pointer types: function variables, method variables, nested function variables and function references. However not every anonymous function is assignable to every function pointer type as it depends on which symbols (if any) are captured from the surrounding scope. Unlike for non-anonymous function or method identifiers this assignment is however always done without the "@"-operator, because aside from calling one can't do much else with anonymous functions.
An anonymous function that captures no symbols at all (except for global symbols or static symbols) is assignable to all four function pointer types. If the anonymous function captures Self then it is no longer assignable to function variables, but still to the other three. And if it captures any local symbol then it's only assignable to nested function variables or function references.
In case of function variables, method variables and nested function variables anonymous functions behave just like their non-anonymous counterparts. The differences appear when they're used with function references which will be highlighted in the next part.

But first some examples:

Code: Pascal  [Select][+][-]
  1. type
  2.   TFunc = function: LongInt;
  3.  
  4. var
  5.   p: TProcedure;
  6.   f: TFunc;
  7.   n: TNotifyEvent;
  8. begin
  9.   procedure(const aArg: String)
  10.   begin
  11.     Writeln(aArg);
  12.   end('Hello World');
  13.  
  14.   p := procedure
  15.        begin
  16.              Writeln('Foobar');
  17.            end;
  18.   p();
  19.  
  20.   n := procedure(aSender: TObject);
  21.        begin
  22.              Writeln(HexStr(Pointer(aSender));
  23.            end;
  24.   n(Nil);
  25.  
  26.   f := function MyRes : LongInt;
  27.        begin
  28.              MyRes := 42;
  29.            end;
  30.   Writeln(f());
  31. end.

Anonymous Function References

As mentioned above the greatest power of the two new features comes when the two are combined: like a nested function an anonymous function can access symbols from the surrounding scope, however unlike for nested functions a anonymous function that has been assigned to a function reference can leave the scope where it has been declared in and it will then take the captured symbols with it.
For this purpose any variable or parameter that is captured by an anonymous function will become part of the implicitely created object instance (which shall be considered opaque) that will be assigned to the function reference instead of belonging to the original function. The original function will then reference these symbols using the object instance instead of its stack frame. This has the implication that changes to the symobls will be reflected in all anonymous function that capture that symbol.

For example:

Code: Pascal  [Select][+][-]
  1. type
  2.   TProc = reference to procedure;
  3.  
  4. procedure Test;
  5. var
  6.   i: LongInt;
  7.   p: TProc;
  8. begin
  9.   i := 42;
  10.   p := procedure
  11.        begin
  12.              Writeln(i);
  13.            end;
  14.            
  15.   p(); // will print 42
  16.  
  17.   i := 21;
  18.  
  19.   p(); // will print 21
  20. end;

Changes will those also be persistent across calls and different anonymous functions as long as they capture the same symbols:

Code: Pascal  [Select][+][-]
  1. type
  2.   TProc = reference to procedure;
  3.  
  4. procedure Test;
  5. var
  6.   i: LongInt;
  7.   p1, p2: TProc;
  8. begin
  9.   i := 42;
  10.   p1 := procedure
  11.         begin
  12.               Writeln(i);
  13.                   i := i * 2;
  14.             end;
  15.            
  16.   p1(); // will print 42
  17.  
  18.   p2 := procedure
  19.         begin
  20.                   Writeln(i);
  21.                 end;
  22.  
  23.   p1(); // will print 84
  24.   p2(); // will print 168
  25. end;

The lifetime of managed types captured by anonymous function references will be handled accordingly (they will stay alive as long as at least one anonymous function that has captured them is alive as well), however special care needs to be taken regarding manual memory management:

Code: Pascal  [Select][+][-]
  1. type
  2.   TProc = reference to procedure;
  3.  
  4. function Test: TProc;
  5. var
  6.   o: TObject;
  7. begin
  8.   o := TObject.Create;
  9.   Result := procedure
  10.             begin
  11.                           Writeln(o.ClassName);
  12.                         end;
  13.   o.Free;
  14. end;

Calling the function reference returned by Test will essentially result in use-after-free. And not freeing “o” at all will result in a memory leak.

Compatibility

The two features are by and large compatible to Delphi's Anonymous Methods. However FPC allows the assignment of anonymous functions to various function pointer types while Delphi restricts them to function references.
Also FPC handles the assignment of function, method and nested function variables to function variables slightly differently. Take the following code:

Code: Pascal  [Select][+][-]
  1. procedure Foo;
  2. begin
  3.   Writeln('Foo');
  4. end;
  5.  
  6. procedure Bar;
  7. begin
  8.   Writeln('Bar');
  9. end;
  10.  
  11. procedure Test;
  12. var
  13.   p: reference to procedure;
  14.   p2: procedure;
  15. begin
  16.   p2 := Foo;
  17.   p := p2;
  18.   p();
  19.   p2 := Bar;
  20.   p();
  21. end;

Delphi essentially generates the following:

Code: Pascal  [Select][+][-]
  1. procedure Test;
  2. var
  3.   p: reference to procedure;
  4.   p2: procedure;
  5. begin
  6.   p2 := Foo;
  7.   p := procedure
  8.        begin
  9.              p2();
  10.            end;
  11.   p();
  12.   p2 := Bar;
  13.   p();
  14. end;

This will result in the following output:

Code: [Select]
Foo
Bar

However FPC will generate the following:

Code: Pascal  [Select][+][-]
  1. procedure Test;
  2. var
  3.   p: reference to procedure;
  4.   p2, tmp: procedure;
  5. begin
  6.   p2 := Foo;
  7.   tmp := p2;
  8.   p := procedure
  9.        begin
  10.              tmp();
  11.            end;
  12.   p();
  13.   p2 := Bar;
  14.   p();
  15. end;

This will result in the following output:

Code: [Select]
Foo
Foo

This is more consistent with assignments of other function pointer types to function pointer types.

The Function References feature is available on all platforms which have the Classes feature available (so essentially everything except AVR) and Anonymous Functions themselves are available on all platforms (excluding the assignment to function references on platforms where these are missing). Yes, this includes platform like DOS where directives like “far” and “near” are handled accordingly (which means that these need to be compatible as well when assigning).

As these two features are rather complicated there might still be a huge bundle of bugs lurking around so I ask you to test them to year heart's content and report found bugs to the issues on GitLab so that we can fix as many of them as possible before the next major version (which is not yet planned, so don't worry ;) ).

Further RTL enhancements like the declaration of TProc<> or the addition of a TThread.Queue() that takes a function reference will come in the near future now that the basics on the compiler side are done. Maybe we can now also tackle ports of libraries like Spring4D and OmniThreadLibrary. There's also the idea to introduce a syntax to control whether symbols are captured by-reference (as currently) or by-value.

Enjoy!
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: Okoba on May 26, 2022, 09:52:59 pm
Thank you so much to the team!
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: marcov on May 26, 2022, 10:09:52 pm
Are the modeswitches already always on for $mode Delphi ?
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: PascalDragon on May 26, 2022, 10:13:07 pm
Are the modeswitches already always on for $mode Delphi ?

Not yet, I first want to spot any mature hurdles with more testers and then we can make them default before the next major release (though at least they shouldn't affect exisiting code except for the point I had mentioned in my announcement post regarding reference, so we can make them default sooner as well *shrugs*).
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: bytebites on May 27, 2022, 08:02:40 am
Oh, it happened in our lifetime.  :D  Thank you.
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: Thaddy on May 27, 2022, 01:20:11 pm
Nice toy! Well done you all.
Here's an old Barry Kelly smartpointer example for Delphi that now compiles in FPC:
Code: Pascal  [Select][+][-]
  1. {$ifdef windows}{$apptype console}{$endif}
  2. {$mode delphi}{$modeswitch functionreferences}{$modeswitch anonymousfunctions}
  3. {$warn 5036 off}
  4. uses
  5.   SysUtils;
  6.  
  7. type
  8.   Tproc = reference to procedure;
  9.   TLifetimeWatcher = class(TInterfacedObject)
  10.   private
  11.     FWhenDone: TProc;
  12.   public
  13.     constructor Create(const AWhenDone: TProc);
  14.     destructor Destroy; override;
  15.   end;
  16.  
  17. { TLifetimeWatcher }
  18.  
  19. constructor TLifetimeWatcher.Create(const AWhenDone: TProc);
  20. begin
  21.   FWhenDone := AWhenDone;
  22. end;
  23.  
  24. destructor TLifetimeWatcher.Destroy;
  25. begin
  26.   if Assigned(FWhenDone) then
  27.     FWhenDone;
  28.   inherited;
  29. end;
  30.  
  31. type
  32.   TSmartPointer<T: class> = record
  33.   strict private
  34.     FValue: T;
  35.     FLifetime: IInterface;
  36.   public
  37.     constructor Create(const AValue: T); overload;
  38.     class operator Implicit(const AValue: T): TSmartPointer<T>;
  39.     property Value: T read FValue;
  40.   end;
  41.  
  42. { TSmartPointer<T> }
  43.  
  44. constructor TSmartPointer<T>.Create(const AValue: T);
  45. begin
  46.   FValue := AValue;
  47.   FLifetime := TLifetimeWatcher.Create(procedure
  48.   begin
  49.     AValue.Free;
  50.   end);
  51. end;
  52.  
  53. class operator TSmartPointer<T>.Implicit(const AValue: T): TSmartPointer<T>;
  54. begin
  55.   Result := TSmartPointer<T>.Create(AValue);
  56. end;
  57.  
  58. procedure UseIt;
  59. var
  60.   x: TSmartPointer<TLifetimeWatcher>;
  61. begin
  62.   x := TLifetimeWatcher.Create(procedure
  63.   begin
  64.     Writeln('I died.');
  65.   end);
  66. end;
  67.  
  68. begin
  69.   try
  70.     UseIt;
  71.     Readln;
  72.   except
  73.     on E:Exception do
  74.       Writeln(E.Classname, ': ', E.Message);
  75.   end;
  76. end.
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: simone on May 27, 2022, 03:05:29 pm
Thanks to the development team. Will this new feature be available in the next release version of fpc?
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: PascalDragon on May 27, 2022, 03:19:01 pm
Thanks to the development team. Will this new feature be available in the next release version of fpc?

It will be in the next major release which is not yet scheduled. The next release will be a minor release (namely 3.2.4).
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: Bi0T1N on May 27, 2022, 09:09:59 pm
I'm glad to see that it has finally arrived in FPC - good work! This should allow us to use several nice Delphi libraries with FPC. 8-)
I think you can also close the issue (https://gitlab.com/freepascal.org/fpc/source/-/issues/24481) now.
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: Thaddy on May 27, 2022, 09:32:11 pm
I'm glad to see that it has finally arrived in FPC - good work! This should allow us to use several nice Delphi libraries with FPC. 8-)
I think you can also close the issue (https://gitlab.com/freepascal.org/fpc/source/-/issues/24481) now.
Not only that: FPC can do more as per PascalDragon's introductary notes...
I have been toying with old D2009 and XE2 examples today and 90% can be made to work in minutes. Much more than I expected.
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: edwinyzh on May 28, 2022, 05:44:29 am
Wonderful! Now all must-have syntax I want from FPC is available!
Thank all you guys!
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: avk on May 28, 2022, 12:34:01 pm
This is really great news, many thanks to the FPC team!
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: Thaddy on May 28, 2022, 03:12:27 pm
If you want to experiment, you need the latest trunk.
These defines can help others as well:
Code: Pascal  [Select][+][-]
  1. {$ifdef windows}{$apptype console}{$endif}
  2. {$mode delphi}{$modeswitch functionreferences}{$modeswitch anonymousfunctions}
  3. {$warn 5036 off}// "Warning: (5036) Local variable "$Capturer" does not seem to be initialized"
  4.  
The "warn 5036 off" is just to suppress a warning about the $capturer variable:
I suppose that will be fixed later? It does not really harm, but I sometimes like to compile with -Sew.
Everything else looks OK, except some advanced examples that use extended RTTI, but that is logical and described in the announcement. It just means that some frameworks do not work yet.
I have by now tested some 20+ examples from Delphi that do work and some homebrew.
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: PascalDragon on May 28, 2022, 06:24:23 pm
I think you can also close the issue (https://gitlab.com/freepascal.org/fpc/source/-/issues/24481) now.

I wanted to look for that already. Thanks for finding it for me. :)

I'm glad to see that it has finally arrived in FPC - good work! This should allow us to use several nice Delphi libraries with FPC. 8-)
I think you can also close the issue (https://gitlab.com/freepascal.org/fpc/source/-/issues/24481) now.
Not only that: FPC can do more as per PascalDragon's introductary notes...
I have been toying with old D2009 and XE2 examples today and 90% can be made to work in minutes. Much more than I expected.

The other 10% would probably be interesting. At least if they don't rely on feature that we currently don't support (extended RTTI, some of the TValue functionality, etc.).

These defines can help others as well:
Code: Pascal  [Select][+][-]
  1. {$ifdef windows}{$apptype console}{$endif}
  2. {$mode delphi}{$modeswitch functionreferences}{$modeswitch anonymousfunctions}
  3. {$warn 5036 off}// "Warning: (5036) Local variable "$Capturer" does not seem to be initialized"
  4.  
The "warn 5036 off" is just to suppress a warning about the $capturer variable:
I suppose that will be fixed later? It does not really harm, but I sometimes like to compile with -Sew.

And why, pray tell, did you not report this? This is the first time I hear of this considering that I wrote a ton of tests... :o Do you have an example where this happens?
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: Thaddy on May 28, 2022, 08:19:38 pm
Of course, Sarah. See my above Barry Kelly example and comment the warn  8-)
But a great achiviement.

The warnings are on:
testanon.pas(51,1) Warning: (5036) Local variable "$Capturer" does not seem to be initialized
And on line 61 the same.

"$Capturer" is not accessible for the normal user.
There is also a note, but I tend to ignore those.
For completeness: "testanon.pas(60,3) Note: (5027) Local variable "x" is assigned but never used" which is not true...
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: AFFRIZA 亜風実 on June 01, 2022, 07:56:54 am
Nice, can't wait to test that.  :D
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: PascalDragon on June 01, 2022, 08:54:39 am
The warnings are on:
testanon.pas(51,1) Warning: (5036) Local variable "$Capturer" does not seem to be initialized
And on line 61 the same.

Can it be that you compile with -O3? (Cause there's another report about this, but without optimizations enabled I can't reproduce this)
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: Thaddy on June 01, 2022, 10:41:24 am
I compiled with -O4 and win64. It should be reproducible in main. With -02 there is no warning.
On linux32/64 debian the warning is also there with -O4
Nothing important since the compiler is built with -Sew and -O2 and that gives no warnings.
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: PascalDragon on June 01, 2022, 01:37:30 pm
I compiled with -O4 and win64. It should be reproducible in main. With -02 there is no warning.
On linux32/64 debian the warning is also there with -O4

Then, next time, please also mention what settings you use to compile, cause that can make a significant difference.
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: JdeHaan on June 17, 2022, 10:58:09 am
Hi, first of all, great to have this new functionality and using it already in several places in my code.

I have a small (for me unsolvable) issue, and was hoping to use the function references/anonymous functions to find a solution.
What is the problem? I’m trying to convert a C++ macro to a Pascal function:

#define MEM(allocator, …) (someFunctionCall(), allocator(__VA_ARGS__))

it is called as follows:

MEM(allocString, “Hello world”);
MEM(allocCell, value);
MEM(allocFunction, codeObject);

The result of the MEM call will be a ‘Value’ type.

so, the macro allows to call different functions with different arguments.

The functions are defined as:

Value allocString(std::String string)
Value allocCell(Value value)
Value allocFunction(CodeObject codeObject)

Any idea how to convert this to Object Pascal?

Regards,
Jeroen
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: marcov on June 17, 2022, 12:41:53 pm
I don't think this is possible. Except maybe with an external macro package like M4.

Macro shenanigans are simply intrinsicly unportable between languages as they operate on the text of the code.
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: PascalDragon on June 17, 2022, 01:24:53 pm
Any idea how to convert this to Object Pascal?

Under the assumption that the allocators only take one argument you can do it like this (this requires main due to the implicit function specialization):

Code: Pascal  [Select][+][-]
  1. program ttest;
  2.  
  3. {$mode objfpc}{$H+}
  4. {$modeswitch implicitfunctionspecialization}
  5.  
  6. type
  7.   Value = record
  8.   end;
  9.  
  10.   generic TAllocator<T> = function(const aArg: T): Value;
  11.  
  12. procedure SomeFunctionCall;
  13. begin
  14.  
  15. end;
  16.  
  17. generic function MEM<T>(allocator: specialize TAllocator<T>; const aValue: T): Value; inline;
  18. begin
  19.   SomeFunctionCall;
  20.   Result := allocator(aValue);
  21. end;
  22.  
  23. function AllocString(const aStr: String): Value;
  24. begin
  25. end;
  26.  
  27. function AllocCell(const aCell: Value): Value;
  28. begin
  29. end;
  30.  
  31. function AllocFunction(const aCodeObject: CodePointer): Value;
  32. begin
  33. end;
  34.  
  35. var
  36.   v: Value;
  37. begin
  38.   v := MEM(@AllocString, 'Hello World');
  39.   v := MEM(@AllocCell, v);
  40.   v := MEM(@AllocFunction, CodePointer(@SomeFunctionCall));
  41. end.

At least assuming I understood correctly what you're trying to do...
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: JdeHaan on June 17, 2022, 02:11:59 pm
Yes, that's pretty much what I need, thanks, I'll give it a try!
So, nothing to do with function references, as mentioned in the new feature...
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: Pascal Gladiator on June 19, 2022, 05:56:58 am
Quote
Under the assumption that the allocators only take one argument you can do it like this (this requires main due to the implicit function specialization):

Firstly I just joined the forum and I couldn't figure out how to reply by quoting selected text. Is this possible?

I just actually posted this exact same question on the FPC mail list before I saw this post. So it works with only one argument but why are arguments not allowed? The type is an undefined generic so it should work in theory and only consider the parameters when the function is specialize
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: PascalDragon on June 19, 2022, 01:12:13 pm
Quote
Under the assumption that the allocators only take one argument you can do it like this (this requires main due to the implicit function specialization):

Firstly I just joined the forum and I couldn't figure out how to reply by quoting selected text. Is this possible?

You just did?

I just actually posted this exact same question on the FPC mail list before I saw this post. So it works with only one argument but why are arguments not allowed? The type is an undefined generic so it should work in theory and only consider the parameters when the function is specialize

It's possible for more arguments as well, but it means that you need to add overloads of MEM that uses different types of function variables. E.g. like this:

Code: Pascal  [Select][+][-]
  1. type
  2.   generic TAllocator1<T1> = function(const aArg1: T1): Value;
  3.   generic TAllocator2<T2> = function(const aArg1: T1; const aArg2: T2): Value;
  4.  
  5. generic function MEM<T1>(allocator: specialize TAllocator1<T1>; const aValue1: T1): Value; inline;
  6. generic function MEM<T1, T2>(allocator: specialize TAllocator2<T1, T2>; const aValue1: T1; const aValue2: T2): Value; inline;
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: Pascal Gladiator on June 20, 2022, 04:47:23 am
For quoted reply I could copy and pasted but notice how it doesn't say who the quote was from. I expected to select some text and then hover the mouse over it and have an option to quote the selection. Pressing the quote button quotes the entire message which I think need to edit by hand.

The question was more about the undefined type and adding parameters like:

Code: Pascal  [Select][+][-]
  1. generic procedure Perform<T>(func: T);
  2. begin
  3. func(1);
  4. end;

I replied and CC'd you on the mail list also since we're both having problems getting messages. I'm using a gmail account btw because my private domain was blocked (Jonas discovered this some years ago) but I think that's our problem. Other users reported this also so maybe Gmail is the culprit for them also.
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: PascalDragon on June 20, 2022, 01:36:24 pm
For quoted reply I could copy and pasted but notice how it doesn't say who the quote was from. I expected to select some text and then hover the mouse over it and have an option to quote the selection. Pressing the quote button quotes the entire message which I think need to edit by hand.

Correct. You quote whole and then edit by hand.

The question was more about the undefined type and adding parameters like:

Code: Pascal  [Select][+][-]
  1. generic procedure Perform<T>(func: T);
  2. begin
  3. func(1);
  4. end;

Then you should have mentioned that in the first place, cause I thought you were referring to what I had written to JdeHaan.

I replied and CC'd you on the mail list also since we're both having problems getting messages. I'm using a gmail account btw because my private domain was blocked (Jonas discovered this some years ago) but I think that's our problem. Other users reported this also so maybe Gmail is the culprit for them also.

It has something to do with SPF records and the domain we send e-mails from. We're already trying to solve this, but as documentation is a bit sparse it's a bit hit-and-miss.
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: JdeHaan on June 21, 2022, 10:16:04 pm
Again thank you PascalDragon for the examples. I did need 1 or 2 arguments. Above that I needed it to be part of an advanced record. The following code runs now perfectly:

Code: Pascal  [Select][+][-]
  1. {$mode objfpc}{$H+}
  2. {$ModeSwitch advancedrecords}
  3. {$ModeSwitch implicitfunctionspecialization}
  4.  
  5. type
  6.   generic TAllocator1<T1> = function(const aArg1: T1): TValue;
  7.   generic TAllocator2<T1, T2> = function(const aArg1: T1; const aArg2: T2): TValue;
  8.  
  9.   TMyRec = record
  10.     procedure someProc;
  11.     generic function MEM<T1>(allocator: specialize TAllocator1<T1>;
  12.       const aValue1: T1): TValue;
  13.     generic function MEM<T1, T2>(allocator: specialize TAllocator2<T1, T2>;
  14.       const aValue1: T1; const aValue2: T2): TValue;
  15.   end;
  16.  
  17. implementation
  18.  
  19. generic function TMyRec.MEM<T1>(allocator: specialize TAllocator1<T1>;
  20.   const aValue1: T1): TValue;
  21. begin
  22.   someProc;
  23.   Result := allocator(aValue1);
  24. end;
  25.  
  26. generic function TMyRec.MEM<T1, T2>(allocator: specialize TAllocator2<T1, T2>;
  27.   const aValue1: T1; const aValue2: T2): TValue;
  28. begin
  29.   someProc;
  30.   Result := allocator(aValue1, aValue2);
  31. end;
  32.  

and then calling the respective MEM functions with 1 or 2 arguments from other functions in the record:

Code: Pascal  [Select][+][-]
  1. Result := MEM(@allocString, 'Hello world!');
  2. Result := MEM(@allocCell, Value);
  3.  
  4. Result := MEM(@allocFunction, Name, Arity);
  5.  

Really nice feature!
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: PascalDragon on June 22, 2022, 09:01:59 am
Really nice feature!

You're welcome.

Please note that you need the ImplicitFunctionSpecialization modeswitch only in the unit where you call MEM, not where you declare it (I know you said that you call MEM from other methods of the same record, so for your real code this doesn't apply, but I just wanted to be sure that you're aware of this).
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: JdeHaan on June 22, 2022, 12:25:09 pm
Noted, thanks!
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: VTwin on July 08, 2022, 02:02:16 am
Please forgive my ignorance, but how are function references superior to

Code: Pascal  [Select][+][-]
  1. type
  2.   TPoint = record
  3.     X : double;
  4.     Y : double;
  5.   end;
  6.   TDistanceFunc = function(p, q: TPoint): double;
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: y.ivanov on July 08, 2022, 02:44:10 pm
Please forgive my ignorance, but how are function references superior to

Code: Pascal  [Select][+][-]
  1. type
  2.   TPoint = record
  3.     X : double;
  4.     Y : double;
  5.   end;
  6.   TDistanceFunc = function(p, q: TPoint): double;

It has something to do with the variable binding (https://docwiki.embarcadero.com/RADStudio/Sydney/en/Anonymous_Methods_in_Delphi#Variable_Binding) I believe.
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: PascalDragon on July 08, 2022, 05:43:56 pm
Please forgive my ignorance, but how are function references superior to

Code: Pascal  [Select][+][-]
  1. type
  2.   TPoint = record
  3.     X : double;
  4.     Y : double;
  5.   end;
  6.   TDistanceFunc = function(p, q: TPoint): double;

Function references can take additional context. Assume that TDistanceFunc is declared as a reference to function instead of a function and you can do the following (stupid example):

Code: Pascal  [Select][+][-]
  1. function OffsetBy(const aPoint: TPoint): TDistanceFunc;
  2. begin
  3.   Result := function(aArg1, aArg2): Double;
  4.     var
  5.       v: TPoint;
  6.     begin
  7.       v.x := aArg1.X - aArg2.X + aPoint.X;
  8.       v.y := aArg1.Y - aArg2.Y + aPoint.Y;
  9.    
  10.       Result := Sqrt(Sqr(v.x) + Sqr(v.y));
  11.     end;
  12. end;
  13.  
  14. var
  15.   f: TDistanceFunc;
  16. begin
  17.   f := OffsetBy(Point(10, 20));
  18.   Writeln(f(Point(5, 3), Point(6, 8));
  19.   f := OffsetBy(Point(5, -1));
  20.   Writeln(f(Point(5, 3), Point(6, 8));
  21. end.
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: y.ivanov on July 08, 2022, 06:36:07 pm
@VTwin
Function references encapsulates the function together with it's lexical context (at least - part of) which is also knows as a "closure" (C#, C++). May be the  most familiar example in FPC is the method delegate (procedure of object), which keeps together the method address and the Self pointer of the instance it belongs. Thus, the receiver object can execute the method in the context of the original object.

Function references just broadens the context by being able to keep it bigger - including variables from the current scope, etc.

But the devil is into the details, and I wonder how useful it will be in practice considering the dynamic nature of the class instances in FPC.
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: VTwin on July 08, 2022, 06:54:39 pm
Thanks!

I appreciate the example PascalDragon.
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: Warfley on July 08, 2022, 08:35:41 pm
To give a more practical example about this, with such a feature you can generalize partial function application which can be used to specialize functions:
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. {$mode objfpc}{$H+}
  4. {$modeswitch functionreferences}
  5. {$modeswitch anonymousfunctions}
  6. uses
  7.   SysUtils;
  8.  
  9. type
  10.   generic TBinaryProcedure<TParam1, TParam2> = reference to procedure(const A: TParam1; const B: TParam2);
  11.   generic TUnaryProcedure<TParam1> = reference to procedure(const A: TParam1);
  12.  
  13. generic function Partial<TParam1, TParam2>(Func: specialize TBinaryProcedure<TParam1, TParam2>; const AValue: TParam1): specialize TUnaryProcedure<TParam2>;
  14. begin
  15.   Result := procedure(const AParam: TParam2)
  16.             begin
  17.               Func(AValue, AParam);
  18.             end;
  19. end;
  20.  
  21. procedure LogToFile(const AFile: THandle; const AMessage: String);
  22. var
  23.   LogMessage: String;
  24. begin
  25.   LogMessage := '[%s] %s%s'.Format([DateTimeToStr(Now), AMessage, LineEnding]);
  26.   FileWrite(AFile, LogMessage[1], LogMessage.Length);
  27. end;
  28.  
  29. var
  30.   Log: specialize TUnaryProcedure<String>;
  31.   fl: THandle;
  32. begin
  33.   // Log to consone out
  34.   Log := specialize Partial<THandle, String>(@LogToFile, StdOutputHandle);
  35.   Log('Console Log');
  36.   // Log to console error
  37.   Log := specialize Partial<THandle, String>(@LogToFile, StdErrorHandle);
  38.   Log('Error Log');
  39.   // Log to file
  40.   fl := FileOpen('log.txt', fmOpenWrite);
  41.   Log := specialize Partial<THandle, String>(@LogToFile, fl);
  42.   Log('File Log');
  43.   ReadLn;
  44. end.
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: y.ivanov on July 09, 2022, 07:17:27 pm
To give a more practical example about this, with such a feature you can generalize partial function application which can be used to specialize functions:
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. {$mode objfpc}{$H+}
  4. {$modeswitch functionreferences}
  5. {$modeswitch anonymousfunctions}
  6. uses
  7.   SysUtils;
  8.  
  9. type
  10.   generic TBinaryProcedure<TParam1, TParam2> = reference to procedure(const A: TParam1; const B: TParam2);
  11.   generic TUnaryProcedure<TParam1> = reference to procedure(const A: TParam1);
  12.  
  13. generic function Partial<TParam1, TParam2>(Func: specialize TBinaryProcedure<TParam1, TParam2>; const AValue: TParam1): specialize TUnaryProcedure<TParam2>;
  14. begin
  15.   Result := procedure(const AParam: TParam2)
  16.             begin
  17.               Func(AValue, AParam);
  18.             end;
  19. end;
  20.  
  21. procedure LogToFile(const AFile: THandle; const AMessage: String);
  22. var
  23.   LogMessage: String;
  24. begin
  25.   LogMessage := '[%s] %s%s'.Format([DateTimeToStr(Now), AMessage, LineEnding]);
  26.   FileWrite(AFile, LogMessage[1], LogMessage.Length);
  27. end;
  28.  
  29. var
  30.   Log: specialize TUnaryProcedure<String>;
  31.   fl: THandle;
  32. begin
  33.   // Log to consone out
  34.   Log := specialize Partial<THandle, String>(@LogToFile, StdOutputHandle);
  35.   Log('Console Log');
  36.   // Log to console error
  37.   Log := specialize Partial<THandle, String>(@LogToFile, StdErrorHandle);
  38.   Log('Error Log');
  39.   // Log to file
  40.   fl := FileOpen('log.txt', fmOpenWrite);
  41.   Log := specialize Partial<THandle, String>(@LogToFile, fl);
  42.   Log('File Log');
  43.   ReadLn;
  44. end.

Weird.

I suppose that is the result of the effort for achieving a partial specialization overcoming limitation of the current generics in FPC, considering this (https://forum.lazarus.freepascal.org/index.php/topic,45818.msg325801.html#msg325801) and this (https://forum.lazarus.freepascal.org/index.php/topic,58753.0.html).

I'd use a generic advanced record for that particular case, just to keep the context (the handle) and to be of managed type. The latter is also not mandatory if you have at hand something like TAuto (https://forum.lazarus.freepascal.org/index.php/topic,46306.0.html). 

I'm a big fan of the functional programming too and I'm seeing the anonymous functions handy for map/fold/filter on containers and also for async/await futures (combined with a STAX?) but I'm not so sure for the function references. 

Maybe they're good for replacing the current method delegates, but that would break the LCL backward compatibility, i.e. it would not happen.
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: Warfley on July 09, 2022, 10:34:30 pm
Weird.

I suppose that is the result of the effort for achieving a partial specialization overcoming limitation of the current generics in FPC, considering this (https://forum.lazarus.freepascal.org/index.php/topic,45818.msg325801.html#msg325801) and this (https://forum.lazarus.freepascal.org/index.php/topic,58753.0.html).
This is the partial application pattern, that is used in function programming languages (https://wiki.haskell.org/Partial_application). You can see it as a generalization of OOP methods.
Take for example this
Code: Pascal  [Select][+][-]
  1. TTest = class
  2.   public procedure Foo (A: Integer);
  3. end;
  4.  
  5. procedure TTest.Foo(A: Integer);
  6. ...
  7.  
  8. // method pointer
  9. var
  10.   MethodPtr: reference to procedure(A: Integer);
  11. begin
  12.   MethodPtr := @MyTest.Foo;
  13.  
The Foo method is then just a function that takes a hidden Self parameter, and the taking of the pointer to that function can be thought of creating a new function that fixes the first parameter to the value MyTest.
So this is generally the same as:
Code: Pascal  [Select][+][-]
  1. procedure TTestFoo(Self: TTest; A: Integer);
  2.  
  3. var
  4.   MethodPtr: reference to procedure(A: Integer);
  5. begin
  6.   MethodPtr := procedure(A: Integer) begin TTestFoo(MyTest, A) end;
The function partial just abstracts this process and might some day with implicit specialization work as easy as simply writing:
Code: Pascal  [Select][+][-]
  1.   MethodPtr := Partial(@TTestFoo, MyTest);

So this is basically the generalization of OOP methods. And as such it allows to give really powerfull tools, because OOP methods are bound to classes (or objects) and inheritance. With partial application, the same principle can be extended to any type and any number of parameters. The example above, makes use of the polymorphism inherent in filehandles, to create one instance for each type, similar to inheriting from one base class multiple times, just for a single function (of course, such sets of functions could be combined, e.g. in a record, which would basically emulate classes and virtual methods).

Together with generics, where as you stated this could be used as partial specialization, the same function could be used to be specialized for different types, where only the interface of the type must be the same. This allows to write basically inheritance like methods but for any types that do not need to be related (or even classes at all), as long as they either have a common interface (like a shared method) or there exist common overloaded functions for this type.

If you go completely mad you could even completely emulate OOP with it:
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. {$mode objfpc}{$H+}
  4. {$ModeSwitch anonymousfunctions}
  5. {$ModeSwitch functionreferences}
  6. {$ModeSwitch nestedprocvars}
  7.  
  8. uses
  9.   SysUtils;
  10.  
  11. type
  12.   TLogFunction = reference to procedure(const AMessage: String);
  13.   TLogLevel = (llDebug=0, llWarning, llError, llNone);
  14.  
  15.   TLogger = record      
  16.     Debug: TLogFunction;
  17.     Warning: TLogFunction;
  18.     Error: TLogFunction;
  19.   end;
  20.  
  21. function CreateLogger(AFile: THandle; LogLevel: TLogLevel): TLogger;
  22.  
  23.   procedure NoLog(const AMessage: String);
  24.   begin end;
  25.  
  26.   procedure LogMessage(const Prefix: String; const AMessage: String);
  27.   var
  28.     LogMessage: String;
  29.   begin
  30.     LogMessage := '%s: [%s] %s%s'.Format([Prefix, DateTimeToStr(Now), AMessage, LineEnding]);
  31.     FileWrite(AFile, LogMessage[1], LogMessage.Length);
  32.   end;
  33.  
  34. begin
  35.   if LogLevel <= llDebug then
  36.     Result.Debug := procedure(const AMessage: String) begin LogMessage('DEBUG', AMessage) end
  37.   else
  38.     Result.Debug := @NoLog;
  39.  
  40.   if LogLevel <= llWarning then
  41.     Result.Warning := procedure(const AMessage: String) begin LogMessage('WARNING', AMessage) end
  42.   else
  43.     Result.Warning := @NoLog;    
  44.  
  45.   if LogLevel <= llError then
  46.     Result.Error := procedure(const AMessage: String) begin LogMessage('ERROR', AMessage) end
  47.   else
  48.     Result.Error := @NoLog;
  49. end;
  50.  
  51. function ConsoleLogger(LogLevel: TLogLevel): TLogger;
  52. begin
  53.   Result := CreateLogger(StdOutputHandle, LogLevel);
  54. end;  
  55.  
  56. function ErrorLogger(LogLevel: TLogLevel): TLogger;
  57. begin
  58.   Result := CreateLogger(StdErrorHandle, LogLevel);
  59. end;
  60.  
  61. var
  62.   Logger: TLogger;
  63. begin
  64.   Logger := ConsoleLogger(llDebug);
  65.   Logger.Debug('Debug Test');
  66.   Logger.Warning('Warning Test');
  67.   Logger.Error('Error Test');
  68.  
  69.   Logger := ErrorLogger(llError);
  70.   Logger.Debug('Debug Test');
  71.   Logger.Warning('Warning Test');
  72.   Logger.Error('Error Test');
  73.   ReadLn;
  74. end.

This has a really interesting property, that is that all the logging functions make use of the Handle, so it is like a private field in a class, but because it is a paramter, as soon as it goes out of scope, you can't access it from outside. It's basically the "purest" form of a private identifier, one that doesn't exist after the declaration.

Aside from that, what I find really interesting about this approach, this is possible with only very few lines of code (basically the whole logger core type, which would be some form of abstract class in OOP, is with all function definitions just 40 lines of code). OOP introduces a lot of boilerplate code. This on the other hand is very slim. So I could imagine using this for some rather small things, where previously I would have used classes with just 1 or 2 (virtual/overriden) functions

Quote
I'm a big fan of the functional programming too and I'm seeing the anonymous functions handy for map/fold/filter on containers and also for async/await futures (combined with a STAX?) but I'm not so sure for the function references. 

Maybe they're good for replacing the current method delegates, but that would break the LCL backward compatibility, i.e. it would not happen.
Function references have a huge andvantage. So I am writing a lot of code that makes use of function pointers, for example STAX, but also my iterators library (https://github.com/Warfley/ObjPasUtils/tree/master/src/iterators), and because I don't want to restrict the user to only one kind of function pointer, I need to define everything multiple times. Look at my functypes unit (https://github.com/Warfley/ObjPasUtils/blob/master/src/functypes/functypes.pas):
Code: Pascal  [Select][+][-]
  1.   generic TFunction<TResult> = function(): TResult;
  2.   generic TFunctionMethod<TResult> = function(): TResult of object;
  3.   generic TFunctionNested<TResult> = function(): TResult is nested;
  4.  
  5.   generic TAnyFunction<TResult> = record
  6.   public type
  7.     TMyType = specialize TAnyFunction<TResult>;
  8.     TMyFunction = specialize TFunction<TResult>;
  9.     TMyFunctionMethod = specialize TFunctionMethod<TResult>;
  10.     TMyFunctionNested = specialize TFunctionNested<TResult>;
  11.  
  12.   public
  13.     class operator :=(AFunc: TMyFunction): TMyType; inline; overload;
  14.     class operator :=(AFunc: TMyFunctionMethod): TMyType; inline; overload;
  15.     class operator :=(AFunc: TMyFunctionNested): TMyType; inline; overload;
  16.  
  17.   public
  18.     function apply(): TResult; inline;
  19.  
  20.   private
  21.     case FunctionType: TFunctionType of
  22.       ftFunction: (FFunction: TMyFunction);
  23.       ftFunctionMethod: (FFunctionMethod: TMyFunctionMethod);
  24.       ftFunctionNested: (FFunctionNested: TMyFunctionNested);
  25.   end;
  26.  
  27. class operator TAnyFunction.:=(AFunc: TMyFunction): TMyType;
  28. begin
  29.   Result.FunctionType := ftFunction;
  30.   Result.FFunction := AFunc;
  31. end;
  32.  
  33. class operator TAnyFunction.:=(AFunc: TMyFunctionMethod): TMyType;
  34. begin
  35.   Result.FunctionType := ftFunctionMethod;
  36.   Result.FFunctionMethod := AFunc;
  37. end;
  38.  
  39. class operator TAnyFunction.:=(AFunc: TMyFunctionNested): TMyType;
  40. begin
  41.   Result.FunctionType := ftFunctionNested;
  42.   Result.FFunctionNested := AFunc;
  43. end;
  44.  
  45. function TAnyFunction.apply(): TResult;
  46. begin
  47.   case FunctionType of
  48.   ftFunction: Result := FFunction();
  49.   ftFunctionMethod: Result := FFunctionMethod();
  50.   ftFunctionNested: Result := FFunctionNested();
  51.   end;
  52. end;
60 lines or so, just to basically implement something that behaves as function references (just worse, because implicit specializations don't work transitively through implicit casts). And I have this for all kinds of functions and procedures from 0 to 5 parameters, It's 600 lines of code, just to emulate what is now provided at language level (and better).
Function references are a huge update, personally I feel they are a bigger upgrade than anonymous functions (as these where previously also possible using nested procedures).

And somewhen in the future I will update both STAX and ObjPasUtils for the new features, this is going to give it a huge upgrade in usability
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: y.ivanov on July 10, 2022, 12:45:56 pm
Weird.

I suppose that is the result of the effort for achieving a partial specialization overcoming limitation of the current generics in FPC, considering this (https://forum.lazarus.freepascal.org/index.php/topic,45818.msg325801.html#msg325801) and this (https://forum.lazarus.freepascal.org/index.php/topic,58753.0.html).
This is the partial application pattern, that is used in function programming languages (https://wiki.haskell.org/Partial_application).
*snip*
I see. Not familiar with Haskell but with the scheme language (https://en.wikipedia.org/wiki/Scheme_(programming_language)) where the same pattern is used to its fullest.

*snip*
So this is basically the generalization of OOP methods. And as such it allows to give really powerfull tools, because OOP methods are bound to classes (or objects) and inheritance. With partial application, the same principle can be extended to any type and any number of parameters.
*snip*
I don't see it as a generalization but as an alternative way to bind code to a context. I'll admit that in the latter you'll have an internally managed context, yes.
 
*snip*

Aside from that, what I find really interesting about this approach, this is possible with only very few lines of code (basically the whole logger core type, which would be some form of abstract class in OOP, is with all function definitions just 40 lines of code). OOP introduces a lot of boilerplate code. This on the other hand is very slim. So I could imagine using this for some rather small things, where previously I would have used classes with just 1 or 2 (virtual/overriden) functions

Quote
I'm a big fan of the functional programming too and I'm seeing the anonymous functions handy for map/fold/filter on containers and also for async/await futures (combined with a STAX?) but I'm not so sure for the function references. 

Maybe they're good for replacing the current method delegates, but that would break the LCL backward compatibility, i.e. it would not happen.
Function references have a huge andvantage. So I am writing a lot of code that makes use of function pointers, for example STAX, but also my iterators library (https://github.com/Warfley/ObjPasUtils/tree/master/src/iterators), and because I don't want to restrict the user to only one kind of function pointer, I need to define everything multiple times. Look at my functypes unit (https://github.com/Warfley/ObjPasUtils/blob/master/src/functypes/functypes.pas):
*snip*
*snip*
60 lines or so, just to basically implement something that behaves as function references (just worse, because implicit specializations don't work transitively through implicit casts). And I have this for all kinds of functions and procedures from 0 to 5 parameters, It's 600 lines of code, just to emulate what is now provided at language level (and better).
*snip*
It reminds me of similar efforts in another language before the variadic templates were added.
 
Anyway, it doesn't look any less weird to me.   

IMHO Such an undertaking to approximate a Pascal so close to a functional language is a bit too much. But it is not that I don't appreciate it from the academic point of view.
Title: Re: Feature announcement: Function References and Anonymous Functions
Post by: Warfley on July 10, 2022, 04:20:20 pm
IMHO Such an undertaking to approximate a Pascal so close to a functional language is a bit too much. But it is not that I don't appreciate it from the academic point of view.
I think that it is not necessarily trying to approximate pascal to a functional language, I think this is neither wise nor actually that possible (especially as the core concepts of functional languages, algebraic type system, fully expression based programs and side effect free programming, are something that must be supported on the very core of the language).
That said, there are some concepts that are helpful in a cross paradigm way, e.g. most of the work in my ObjPasUtils is actually just creating such iterator functionality like map, filter, etc. for abritrary types, which I think is something that would be really helpful (there are libraries like LGenerics that provide these functions for their own types, but what I try to build is something that can be applied accross types to work with everything from arrays over TList to any custom class).
This at it's core is not really functional programming (java has it for goodness sake, and I think you don't get further away from functional programming than with java), just because it uses function pointers. It's just something that will now be much easier.

That said, there are also a lot of more academic, the OOP emulation shown above, is basically the concept of prototype based OOP as used in Javascript or Python, and I think can be really helpful in some scenarios (but is not a cureal for every use-case, I think this gets unreadable if the size of the class is larger than 1-2 functions). So how useful this will be is to be shown.
And then there are other things like implementing monads, which could be very interesting from an academic point of view, but will be stretching the language probably to a breaking point (I want to see if I can basically create something like the monadic chaining operator for OOP, where where you could write something like csv := Chain(TStringList.Add, TStringList.Add, TStringList.SetDelimiter, TStringList.GetDelimitedText)(TStringList.Create, 'Item1', 'Item2', ',').

But I like to think that at least the stuff already implemented in ObjPasUtils is useful and not just an academic exercise.
TinyPortal © 2005-2018