Am 27.04.2021 um 17:44 schrieb Ryan Joseph via fpc-devel:
Continued from our discussion at https://bugs.freepascal.org/view.php?id=24481.
if the compiler devs will allow me as soon as this is finished I want to allow
the existing nested functions functionality to work with anonymous functions,
so at the very least we don't need to generate the expensive interface based
object which often times is not even needed. At that point we would need to
make nested functions inline-able, which they are currently not. But we're not
there yet so lets not complicated anything by proposing extensions to a feature
that doesn't even exist yet.
Sven replies:
Getting rid of the interface only works in very narrow circumstances that are
so seldom in real world code that it is not worth the effort.
I'm referring to my test I did a few years ago
(https://github.com/graemeg/freepascal/compare/master...genericptr:anon_funcs)
where I say we can use existing nested functions as a closure when passing is
not required. As you can see I already implemented this quite easily but it is
not related to the new forthcoming closures feature. I did in fact try to
replace the interface with a record on the old closures branch but I ran into
many problems I decided it wasn't the best route.
Indeed there are many times where we don't want a heap allocated interface you
can pass around but rather a simple inline function pointer like below.
Consider this loop is run 60 times a second and allocating a useless class
every time for no gain. This could easily be 1000*60=60,000 constructions and
allocations of a class.
for i := 0 to entities.Count - 1 do
begin
value := entities[i];
value.SortEntities(function(a, b: TEntity): integer
begin
// do stuff
end
);
end;
So anyways what I propose is if a closure is never passed outside of scope
(i.e. temporary) then use anonymous nested functions instead (like in my GitHub
branch). If this is an acceptable approach I will personally do what is
required to get it implemented along side the real closures.
As soon as *any* function is passed to a "reference to
procedure/function" it *must* be an interface, because that's how
"reference to procedure/function" is internally implemented. Anything
that's calling a "reference to procedure/function" is expecting to call
an interface method, thus it *must* be an interface. Also if you capture
anything then that *must* be contained in a capture context, because
whatever you pass that function reference to might store that for later
calling and then the stack context might be long gone. The only
situations where the compiler might optimize this is if it's inside the
same function, maybe inside the same implementation section of the unit
or possibly if WPO is involved (with a dedicated WPO pass), but those
are complex optimizations.
Also your example is wrong, cause it will not create an interface for
each loop iteration. Instead the pseudo code essentially looks like this:
=== code begin ===
procedure Foo;
type
ISort = interface
function Invoke(a, b: TEntity): Integer;
end;
TCaptureObject = class(TInterfacedObject, ISort)
function Invoke(a, b: TEntity): Integer;
end;
function TCaptureObject.Invoke(a, b: TEntity): Integer;
begin
// do stuff
end;
var
context: TCaptureObject;
begin
context := TCaptureObject.Create;
for i := 0 to entities.Count - 1 do
begin
value := entities[i];
value.SortEntities(ISort(context));
end;
end;
=== code end ===
If you capture variables they'll be part of TCaptureObject instead of
the stack.
Regards,
Sven
_______________________________________________
fpc-devel maillist - fpc-devel@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel