Forum > General

Strange capture denial and acceptance for Anonymous Functions and Function refs

(1/2) > >>

Bogen85:
This does not work, a direct capture of nested procedure.

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---{$mode delphi}{$modeswitch anonymousfunctions}{$modeswitch functionreferences} program nocapture; type  tSimpleProc = reference to procedure; procedure main;   var    tcf: tSimpleProc;    x: integer;   procedure nested;    begin      writeln ('Hello from nested! x: ', x);      inc(x);    end;   begin    x := 100;     tcf := procedure begin nested; end;    tcf();    tcf();   end; begin  main;end. 
The above results in:

Free Pascal Compiler version 3.3.1 [2022/09/26] for x86_64
Copyright (c) 1993-2022 by Florian Klaempfl and others
Target OS: Linux for x86-64
Compiling src/nocapture.pas
nocapture.pas(25,28) Error: Symbol "nested" can not be captured
nocapture.pas(34) Fatal: There were 1 errors compiling module, stopping
Fatal: Compilation aborted
Error: /home/shared-development/fpc_usr/lib/fpc/3.3.1/ppcx64 returned an error exitcode


This does work, example includes indirect capture to get the nested procedure to be captured.

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---{$mode delphi}{$modeswitch anonymousfunctions}{$modeswitch functionreferences} program doescapture; type  tSimpleProc = reference to procedure(const n: integer); procedure main0;   var    tcf: tSimpleProc;    tcf2: tSimpleProc;    x: integer;   procedure nested(const n: integer);    begin      writeln ('Hello from nested! ', n, ' x: ', x);      inc(x);    end;   begin    tcf := nested;    x := 100;     procedure(const aArg: String) begin Writeln(aArg); end('Hello World');    procedure begin nested(1); end();    tcf2 := procedure(const n: integer) begin tcf(n); end;    tcf2(2);  end;  procedure main1;  var    x: integer;   begin    x := 132;    procedure begin writeln ('x = ', x) end();  end; begin  main0;  main1;end. 
Produces this output:


Hello World
Hello from nested! 1 x: 100
Hello from nested! 2 x: 101
x = 132

Mr.Madguy:
First of all, I'm not sure if FPC fully supports closures now.

Second thing - Delphi is considered to be standard and it throws the same error. Because, I guess, Nested relies on Main's stack frame, so it can be called from Main only.

Third thing - Delphi throws the same error for your second program too. I don't know, why it works for you. It shouldn't. Because it's exactly the same situation.


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---tcf := nested; This should be internally compiled into exactly the same:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---tcf := procedure(const n: integer)     begin         nested(n);    end; 
May be it works due to some optimizations, so code is simply unwrapped?

You should understand, how closures work. Your program is turned into:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---type  //There is no such thing, but, I guess, you get this idea  //It's:  //tSimpleProc = record  //  Intf:IInterface  //  Proc:procedure;  //end;  //When it's called - it's something like tcf.Proc(tcf.Intf, ...);  //I.e. tcf.Intf is passed as Self  tSimpleProc = procedure of interface;   ITemp = interface    procedure Temp;  end;   TTemp = class(TInterfacedObject, ITemp)    procedure Temp;  end; procedure TTemp.Temp;begin  //nested is inaccessible from here  //How can local variable be accessible from here?  //It's added as field to TTemp class  //I.e. captured  //Isn't possible for nested procedure  nested;end; procedure main; var  tcf: tSimpleProc;  x: integer;   procedure nested;  begin    WriteLn ('Hello from nested! x: ', x);    Inc(x);  end; begin  tcf := TTemp.Create.Temp;  tcf();  tcf();end; 
P.S. Such behavior can actually be achieved via turning nested into TTemp's method. But Delphi's docs don't mention such behavior and I'm not sure, what side effects it can have.

Bogen85:

--- Quote from: Mr.Madguy on September 27, 2022, 05:39:09 pm ---First of all, I'm not sure if FPC fully supports closures now.

--- End quote ---

@Mr.Madguy:

From what I can tell, FPC (built from latest git, does support closures).
I was playing around them and they definitely work as if they are closures (I did various things where I passed them off into various contexts and they worked as expected for closures), which is how I discovered the above behavior.

See this: https://forum.lazarus.freepascal.org/index.php/topic,59468.0.html Topic: Feature announcement: Function References and Anonymous Functions

Specifically this reply:


--- Quote from: 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.

--- End quote ---

I don't have access to Delphi (because I don't have any reasonable access to any Windows system), so I can't compare the two (FPC and Delphi)


--- Code: Bash  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---$ fpc i-want-the-versionFree Pascal Compiler version 3.3.1 [2022/09/26] for x86_64Copyright (c) 1993-2022 by Florian Klaempfl and othersTarget OS: Linux for x86-64Compiling i-want-the-versionFatal: Cannot open file "i-want-the-version"Fatal: Compilation abortedError: /home/shared-development/fpc_usr/lib/fpc/3.3.1/ppcx64 returned an error exitcode 
I built FPC yesterday from git to play around with function references and anonymous functions

Mr.Madguy:

--- Quote from: Bogen85 on September 27, 2022, 09:38:42 pm ---From what I can tell, FPC (built from latest git, does support closures).
I was playing around them and they definitely work as if they are closures (I did various things where I passed them off into various contexts and they worked as expected for closures), which is how I discovered the above behavior.
--- End quote ---
Great news then. My Delphi project relies on this feature. I guess, it has been last FPC's incompatibility with modern Delphi versions. I'll try to compile my project via FPC, when I'll have time to do it.

But again, while closures are treated as nested procedures/functions, nested procedures/functions are accessible from each other and in theory capturing nested procedures/functions is possible - such behavior isn't described in Delphi's docs, so it shouldn't be supported. Only variables can be captured.

According to announce your second program works because:
1) Temp variables are created implicitly, when procedure/function types are used instead of closures - they're captured instead of procedures/functions themselves (Delphi incompatible)
2) Unassigned closure is treated as simple nested procedure/function

PascalDragon:

--- Quote from: Mr.Madguy on September 27, 2022, 05:39:09 pm ---First of all, I'm not sure if FPC fully supports closures now.
--- End quote ---

As mentioned in the link provided by Bogen85, yes, it does since end of May.


--- Quote from: Mr.Madguy on September 27, 2022, 05:39:09 pm ---Second thing - Delphi is considered to be standard and it throws the same error. Because, I guess, Nested relies on Main's stack frame, so it can be called from Main only.

Third thing - Delphi throws the same error for your second program too. I don't know, why it works for you. It shouldn't. Because it's exactly the same situation.
--- End quote ---

FPC supports a bit more functionality than Delphi does. E.g. assigning a nested function to a function reference is perfectly fine, because the compiler can rework the code so that it will work.


--- Quote from: Mr.Madguy on September 27, 2022, 05:39:09 pm ---
--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---tcf := nested; This should be internally compiled into exactly the same:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---tcf := procedure(const n: integer)     begin         nested(n);    end; 
--- End quote ---

Not quite. If you assign a nested function to a function reference the end result will essentially be this:


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---procedure main;type  tSimpleProc = interface    procedure Invoke(const n: integer);  end;   TCapturer = class(TInterfacedObject, tSimpleProc)    x: integer;    procedure nested(const n: integer);     procedure tSimpleProc.Invoke = nested;  end;   procedure tSimpleProc.Invoke(const n: integer)  begin    writeln ('Hello from nested! ', n, ' x: ', x);    inc(x);  end; var  capturer: TCapturer;  capturer_keepalive: IUnknown;   procedure nested(const n: integer);  begin    capturer.nested(n);  end; var  tcf: TSimpleProc;begin  capturer := TCapturer.Create;  capturer_keepalive := capturer;  tcf := capturer as tSimpleProc;end;
Essentially it moves the code of the original nested function into a method of the capturer class and has the nested function call that instead. This allows the nested function to be used as a function reference and as a nested function at the same time.


--- Quote from: Mr.Madguy on September 27, 2022, 05:39:09 pm ---You should understand, how closures work. Your program is turned into:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---type  //There is no such thing, but, I guess, you get this idea  //It's:  //tSimpleProc = record  //  Intf:IInterface  //  Proc:procedure;  //end;  //When it's called - it's something like tcf.Proc(tcf.Intf, ...);  //I.e. tcf.Intf is passed as Self  tSimpleProc = procedure of interface; 
--- End quote ---

Not quite. There is no concept of “procedure of interface”, instead a “reference to procedure” is turned into an interface with a single method Invoke. You can see this, because you can manually implement a function reference in a custom class just like an ordinary interface or you can even inherit from it.


--- Quote from: Mr.Madguy on September 28, 2022, 08:43:32 am ---1) Temp variables are created implicitly, when procedure/function types are used instead of closures - they're captured instead of procedures/functions themselves (Delphi incompatible)
--- End quote ---

It's not “Delphi incompatible”, because Delphi-compatible code still works. However it's an enhancement to what Delphi allows.


--- Quote from: Mr.Madguy on September 28, 2022, 08:43:32 am ---2) Unassigned closure is treated as simple nested procedure/function

--- End quote ---

An unassigned anonymous method works in Delphi as well (as long as you only capture stuff that Delphi supports ;) ). Delphi however converts that to a method of the capturer object nevertheless and calls it. FPC is a bit more cheap here and simply calls it like a nested function, cause for the user it will have the same effect.


--- Quote from: Bogen85 on September 27, 2022, 01:39:39 am ---This does not work, a direct capture of nested procedure.
--- End quote ---

I had not thought about that use case, but in essence it should be possible as well, considering that it works when assigning a nested function. Please do a feature request on our bug tracker.

Navigation

[0] Message Index

[#] Next page

Go to full version