Recent

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

VTwin

  • Hero Member
  • *****
  • Posts: 1140
  • 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 12.1: Lazarus 2.2.4 (64 bit Cocoa M1)
Ubuntu 18.04.3: Lazarus 2.2.4 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.2.4 (64 bit on VBox)

y.ivanov

  • Hero Member
  • *****
  • Posts: 562
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: 4765
  • 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

  • Hero Member
  • *****
  • Posts: 562
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: 1140
  • 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 12.1: Lazarus 2.2.4 (64 bit Cocoa M1)
Ubuntu 18.04.3: Lazarus 2.2.4 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.2.4 (64 bit on VBox)

Warfley

  • Hero Member
  • *****
  • Posts: 943
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');
  43.   ReadLn;
  44. end.

y.ivanov

  • Hero Member
  • *****
  • Posts: 562
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');
  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 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: 943
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');
  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
« Last Edit: July 09, 2022, 11:13:06 pm by Warfley »

y.ivanov

  • Hero Member
  • *****
  • Posts: 562
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: 943
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 »

Peter H

  • Full Member
  • ***
  • Posts: 199
Re: Feature announcement: Function References and Anonymous Functions
« Reply #40 on: August 27, 2022, 08:20:54 pm »
Hi, I noticed this:
Code: Pascal  [Select][+][-]
  1.    
  2. procedure(const aArg: String)
  3.     begin
  4.          Writeln(aArg);
  5.     end('Hello World');
  6.  

is not supported by codetools.
It says:
project1.lpr(26,5) Error: unexpected keyword "procedure"

Also this compiles, but is not accepted by codetools:
Code: Pascal  [Select][+][-]
  1. p:= function foo:integer
  2.       begin
  3.          foo:= 4711;
  4.       end;
  5.  

It does accept this:
Code: Pascal  [Select][+][-]
  1. i:= function:integer
  2.       begin
  3.          Result:= 4711;
  4.       end();
  5.  

Just for the records, it is not an important problem.

Edit:
Compilable and runnable example attached.
« Last Edit: August 29, 2022, 01:41:45 pm by Peter H »

PascalDragon

  • Hero Member
  • *****
  • Posts: 4765
  • Compiler Developer
Re: Feature announcement: Function References and Anonymous Functions
« Reply #41 on: August 28, 2022, 06:07:02 pm »
Best report it as an issue for Lazarus then.

Peter H

  • Full Member
  • ***
  • Posts: 199
Re: Feature announcement: Function References and Anonymous Functions
« Reply #42 on: August 29, 2022, 04:26:29 am »
I have done this now, was a lot of work create account.
GitLab is not for beginners and casual programmers like me.
I put the code in, cleanly formatted as seen here, and when I submitted it formatting was destroyed.
Then you have to read several pages about GitLab Markdown language before you learn how to fix this.

https://gitlab.com/freepascal.org/lazarus/lazarus/-/issues/39871
« Last Edit: August 29, 2022, 07:21:14 am by Peter H »

 

TinyPortal © 2005-2018