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
TTest = class
public procedure Foo (A: Integer);
end;
procedure TTest.Foo(A: Integer);
...
// method pointer
var
MethodPtr: reference to procedure(A: Integer);
begin
MethodPtr := @MyTest.Foo;
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:
procedure TTestFoo(Self: TTest; A: Integer);
var
MethodPtr: reference to procedure(A: Integer);
begin
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:
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:
program Project1;
{$mode objfpc}{$H+}
{$ModeSwitch anonymousfunctions}
{$ModeSwitch functionreferences}
{$ModeSwitch nestedprocvars}
uses
SysUtils;
type
TLogFunction = reference to procedure(const AMessage: String);
TLogLevel = (llDebug=0, llWarning, llError, llNone);
TLogger = record
Debug: TLogFunction;
Warning: TLogFunction;
Error: TLogFunction;
end;
function CreateLogger(AFile: THandle; LogLevel: TLogLevel): TLogger;
procedure NoLog(const AMessage: String);
begin end;
procedure LogMessage(const Prefix: String; const AMessage: String);
var
LogMessage: String;
begin
LogMessage := '%s: [%s] %s%s'.Format([Prefix, DateTimeToStr(Now), AMessage, LineEnding]);
FileWrite(AFile, LogMessage[1], LogMessage.Length);
end;
begin
if LogLevel <= llDebug then
Result.Debug := procedure(const AMessage: String) begin LogMessage('DEBUG', AMessage) end
else
Result.Debug := @NoLog;
if LogLevel <= llWarning then
Result.Warning := procedure(const AMessage: String) begin LogMessage('WARNING', AMessage) end
else
Result.Warning := @NoLog;
if LogLevel <= llError then
Result.Error := procedure(const AMessage: String) begin LogMessage('ERROR', AMessage) end
else
Result.Error := @NoLog;
end;
function ConsoleLogger(LogLevel: TLogLevel): TLogger;
begin
Result := CreateLogger(StdOutputHandle, LogLevel);
end;
function ErrorLogger(LogLevel: TLogLevel): TLogger;
begin
Result := CreateLogger(StdErrorHandle, LogLevel);
end;
var
Logger: TLogger;
begin
Logger := ConsoleLogger(llDebug);
Logger.Debug('Debug Test');
Logger.Warning('Warning Test');
Logger.Error('Error Test');
Logger := ErrorLogger(llError);
Logger.Debug('Debug Test');
Logger.Warning('Warning Test');
Logger.Error('Error Test');
ReadLn;
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
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):
generic TFunction<TResult> = function(): TResult;
generic TFunctionMethod<TResult> = function(): TResult of object;
generic TFunctionNested<TResult> = function(): TResult is nested;
generic TAnyFunction<TResult> = record
public type
TMyType = specialize TAnyFunction<TResult>;
TMyFunction = specialize TFunction<TResult>;
TMyFunctionMethod = specialize TFunctionMethod<TResult>;
TMyFunctionNested = specialize TFunctionNested<TResult>;
public
class operator :=(AFunc: TMyFunction): TMyType; inline; overload;
class operator :=(AFunc: TMyFunctionMethod): TMyType; inline; overload;
class operator :=(AFunc: TMyFunctionNested): TMyType; inline; overload;
public
function apply(): TResult; inline;
private
case FunctionType: TFunctionType of
ftFunction: (FFunction: TMyFunction);
ftFunctionMethod: (FFunctionMethod: TMyFunctionMethod);
ftFunctionNested: (FFunctionNested: TMyFunctionNested);
end;
class operator TAnyFunction.:=(AFunc: TMyFunction): TMyType;
begin
Result.FunctionType := ftFunction;
Result.FFunction := AFunc;
end;
class operator TAnyFunction.:=(AFunc: TMyFunctionMethod): TMyType;
begin
Result.FunctionType := ftFunctionMethod;
Result.FFunctionMethod := AFunc;
end;
class operator TAnyFunction.:=(AFunc: TMyFunctionNested): TMyType;
begin
Result.FunctionType := ftFunctionNested;
Result.FFunctionNested := AFunc;
end;
function TAnyFunction.apply(): TResult;
begin
case FunctionType of
ftFunction: Result := FFunction();
ftFunctionMethod: Result := FFunctionMethod();
ftFunctionNested: Result := FFunctionNested();
end;
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