Implement closures
Original Reporter info from Mantis: Vasiliy Kevroletin
-
Reporter name: Vasiliy Kevroletin
Original Reporter info from Mantis: Vasiliy Kevroletin
- Reporter name: Vasiliy Kevroletin
Description:
Implementation of closures was started in http://svn.freepascal.org/cgi-bin/viewvc.cgi/branches/blaise/closures/. Last update in branch was more than year ago. Main part of implementation which was in compiler/pnameless.pas wasn't submitted into svn.
I decided to continue work and already done some changes. My work can be found in https://github.com/vkevroletin/freepascal/tree/closures-via-interfaces (or in attached patch). Current implementation allows to write and use anonymous functions, but don't allows to capture variables (it allocates storage for captured variables but doesn't move variables into storage).
I didn't perform proper regression testing, but write few test for things which already works. You can find it in https://github.com/vkevroletin/freepascal/tree/closures-via-interfaces/devtest/ (I will rework and move test to right place later).
* Details of implementation:
I described details of Delhpi's anonymous methods behavior here:
https://github.com/vkevroletin/Diploma/blob/master/anonym_method_delphi.pdf?raw=true.
The biggest problem with closure is memory management. Captured local variables should be allocated on the heap but not on the stack.
Since captured variables allocated on the heap someone should deallocate used memory. Manual memory management will be very complicated because:
+ different closures can capture same variables, anyway we would implement reference counting for captured variables
+ closures often appears in parameters of other function; pascal's users would decide who is responsible for deallocating closure: caller or callee;
That is why closure should be managed type with automatic memory management.
To simplify implementation it's possible to use existing managed type: COM interface.
Because closure should store captured variables we
+ create object (let's call it frame object)
+ move captured variables from stack into object which is allocated on the heap
+ closure's code is a method of this object
But we don't use frame object directly. Instead we create interface which contains single method. So variable-reference to closure will contain single value: reference to interface which contains one method(actually 4 methods: 3 methods from IUnknown and 1 method - closure code). So using reference to this interface compiler will do memory-managment magic, and we will be able to call closure's code.
* How it's done in my pilot implementation:
I didn't create new typedef classes. I used existing tobjectdef which is odt_interfacecom. I added boolean flag isClosure to tobjectdef. It's used in typecheck and during printing of proper error messages(most probably it should be changed?).
Also there is one important detail: I don't create frame object for each closure. Instead I create frame object for each subroutine which contains closures. Delhpi does like this because delhpi doesn't support capturing by value. This should be changed to support capturing by value, but this change will not break approach to use interfaces.
- Declaration of variable
var p: reference to procedure(num: Integer);
p will hold tobjectdef wich is odt_interfacecom. It have single method: procedure Invoke(num: Integer)
- Anonymous function (closure code)
procedure Outer;
begin
...
function (arg: Integer) begin
...
end;
end;
For Outer procedure frame object will be created. Frame object is tobjectdef. Later each closure will become method of frame object. For each closure we create interface which contains single method. Frame object implements this interface. Instead of anonymous function's body we will return frame object's implementation of particular interface. This interface contains single function.
- Assignment
p := function(arg: Integer) begin end;
p is interface. Anonymous function's body is replaced by particular interface of frame object. These interfaces have same structure but different names. Don't care that they have different names: simply use one instead of another, this will work. But I don't sure about Jvm. I don't know a lot about JVM: only that it's typesafe and have not pointer's arithmetic. So here I need advice.
* Questions
- Most important question: is implementation of closures via interfaces is ok? Delphi did closures via interfaces. Will we do in same way? I think this is good and simple approach which solves problem with memory management.
- Next question about implementation: could you please review existing changes and note most important problems.
Thanks,
Vasiliy K.
Mantis conversion info:
- Mantis ID: 24481
- OS: all
- Platform: all
- Version: 2.7.1
- Monitored by: » Ask (Alexander S. Klenin), » Vasiliy Kevroletin (Vasiliy Kevroletin), » Hixie (Ian Hickson), » PaulIsh (Paul Ishenin), » xmen (xmen), » grighome (Grigoriy), » hnb (Maciej Izak), » urhen (NoName), » kazalex (Kazantsev Alexey), » @benibela (Benito van der Zander), » @genericptr (Ryan Joseph), » @onpok (Ondrej Pokorny), » Vincent (Vincent Snijders), » Cyrax (Cyrax), » Akira1364 (Akira1364), » @avagames (George), » rd0x (rd0x), » AntonK (Anton Kavalenka), » @MageSlayer (Denis Golovan), » @CuriousKit (J. Gareth Moreton), » @PascalDragon (Sven Barth)