### Bookstore

 Computer Math and Games in Pascal (preview) Lazarus Handbook

### Author Topic: Feature announcement: Function References and Anonymous Functions  (Read 6820 times)

#### VTwin

• Hero Member
• Posts: 1111
• Former Turbo Pascal 3 user
##### Re: Feature announcement: Function References and Anonymous Functions
« Reply #30 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;
“Talk is cheap. Show me the code.” -Linus Torvalds

Free Pascal Compiler 3.2.2
macOS 10.13.6: Lazarus 2.2.0 (64 bit Cocoa)
macOS 12.0.1: Lazarus 2.2.0 (64 bit Cocoa M1)
Ubuntu 18.04.3: Lazarus 2.2.0 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.2.0 (64 bit on VBox)

#### y.ivanov

• Sr. Member
• Posts: 427
##### Re: Feature announcement: Function References and Anonymous Functions
« Reply #31 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 I believe.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

#### PascalDragon

• Hero Member
• Posts: 4266
• Compiler Developer
##### Re: Feature announcement: Function References and Anonymous Functions
« Reply #32 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.

#### y.ivanov

• Sr. Member
• Posts: 427
##### Re: Feature announcement: Function References and Anonymous Functions
« Reply #33 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.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

#### VTwin

• Hero Member
• Posts: 1111
• Former Turbo Pascal 3 user
##### Re: Feature announcement: Function References and Anonymous Functions
« Reply #34 on: July 08, 2022, 06:54:39 pm »
Thanks!

I appreciate the example PascalDragon.
“Talk is cheap. Show me the code.” -Linus Torvalds

Free Pascal Compiler 3.2.2
macOS 10.13.6: Lazarus 2.2.0 (64 bit Cocoa)
macOS 12.0.1: Lazarus 2.2.0 (64 bit Cocoa M1)
Ubuntu 18.04.3: Lazarus 2.2.0 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.2.0 (64 bit on VBox)

#### Warfley

• Hero Member
• Posts: 907
##### Re: Feature announcement: Function References and Anonymous Functions
« Reply #35 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');
44. end.

#### y.ivanov

• Sr. Member
• Posts: 427
##### Re: Feature announcement: Function References and Anonymous Functions
« Reply #36 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');
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 and this.

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

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.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

#### Warfley

• Hero Member
• Posts: 907
##### Re: Feature announcement: Function References and Anonymous Functions
« Reply #37 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 and this.
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');
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
« Last Edit: July 09, 2022, 11:13:06 pm by Warfley »

#### y.ivanov

• Sr. Member
• Posts: 427
##### Re: Feature announcement: Function References and Anonymous Functions
« Reply #38 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 and this.
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 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.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

#### Warfley

• Hero Member
• Posts: 907
##### Re: Feature announcement: Function References and Anonymous Functions
« Reply #39 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.
« Last Edit: July 10, 2022, 04:23:54 pm by Warfley »