Thread overview | |||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
May 21, 2017 Value closures (no GC allocation) | ||||
---|---|---|---|---|
| ||||
Hello everyone, I recently started learning D (I come from a Modern C++ background) and I was curious about closures that require GC allocation. I wrote this simple example: auto bar(T)(T x) @nogc { return x(10); } auto foo(int x) @nogc { return bar((int y) => x + y + 10); } int main() @nogc { return foo(10); } It doesn't compile with the following error: Error: function example.foo is @nogc yet allocates closures with the GC example.foo.__lambda2 closes over variable x at [...] Live example on godbolt: https://godbolt.org/g/tECDh4 I was wondering whether or not D could provide some syntax that allowed the user to create a "value closure", similar to how C++ lambdas work. How would you feel about something like: auto bar(T)(T x) @nogc { return x(10); } auto foo(int x) @nogc { return bar([x](int y) => x + y + 10); } int main() @nogc { return foo(10); } The syntax: [x](int y) => x + y + 10 would mean "create a 'value closure' that captures `x` by value inside it". It would be equivalent to the following program: struct AnonymousClosure { int captured_x; this(int x) @nogc { captured_x = x; } auto opCall(int y) @nogc { return captured_x + y + 10; } } auto foo(int x) @nogc { return bar(AnonymousClosure(x)); } Which is very similar to how C++ lambdas work. This would allow closures to be used in @nogc contexts with minimal syntactical overhead over classical closures. Live example on godbolt: https://godbolt.org/g/ML2dlP What are your thoughts? Has something similar been proposed before? |
May 21, 2017 Re: Value closures (no GC allocation) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Vittorio Romeo | On Sunday, 21 May 2017 at 00:33:30 UTC, Vittorio Romeo wrote: > auto bar(T)(T x) @nogc Make that `scope T x` and it will compile, using stack allocation. Only worry is that you must not escape a reference to the delegate; you are only allowed to use it in that function's scope. > What are your thoughts? Has something similar been proposed before? It has, and I actually don't hate it, but I also don't think it is necessary because of the `scope` storage class being one option and just manually writing out the struct functor being another viable one. The c++ syntax sugar is nice, but I can live without it. |
May 21, 2017 Re: Value closures (no GC allocation) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Adam D. Ruppe | On Sunday, 21 May 2017 at 00:43:58 UTC, Adam D. Ruppe wrote:
>> What are your thoughts? Has something similar been proposed before?
>
> It has, and I actually don't hate it, but I also don't think it is necessary because of the `scope` storage class being one option and just manually writing out the struct functor being another viable one. The c++ syntax sugar is nice, but I can live without it.
It's not just syntax sugar. We don't always need by-reference captures. And it would be nice to be able to store the delegate longer than current scope...
|
May 21, 2017 Re: Value closures (no GC allocation) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Adam D. Ruppe | On Sunday, 21 May 2017 at 00:43:58 UTC, Adam D. Ruppe wrote: > On Sunday, 21 May 2017 at 00:33:30 UTC, Vittorio Romeo wrote: > > just manually writing out the struct functor being another viable one. The c++ syntax sugar is nice, but I can live without it. Assembler is viable as well, but it's not nice to manually do what a computer can do faster and better, especially if it's an artificial limitation. But I do agree that scope is a solution in this specific case. There was also some discussion of a syntax to use allocators in closures (of course nothing even close to definitive) here https://forum.dlang.org/thread/blvfxcbfzoyxowsfzlhn@forum.dlang.org Btw I'm happy to see you finally picked up D. You are going to enjoy it a lot! |
May 21, 2017 Re: Value closures (no GC allocation) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Stanislav Blinov | On Sunday, 21 May 2017 at 00:49:23 UTC, Stanislav Blinov wrote:
> And it would be nice to be able to store the delegate longer than current scope...
Important to note that this thing won't be a delegate, since delegates don't have room to store by-value captures. It can still be passed to isCallable templates and such, but it will be a struct of some sort, and you *can* write out the struct now, it just has more tedious syntax.
I don't object to the C++ syntax being added though, I just want to remind everyone that the result is possible with a struct.
|
May 21, 2017 Re: Value closures (no GC allocation) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Adam D. Ruppe | Thanks for the suggestions regarding the `scope` parameter attribute. I'll look into it!
On Sunday, 21 May 2017 at 01:30:51 UTC, Adam D. Ruppe wrote:
> On Sunday, 21 May 2017 at 00:49:23 UTC, Stanislav Blinov wrote:
> [...] you *can* write out the struct now, it just has more tedious syntax.
This exact statement applied to C++ before C++11, but the introduction of lambda expression significantly changed the way people write and think about C++. Sometimes syntactic sugar can have huge impact on a language.
I think that creating anonymous structs on the spot (value type closures) is not a replacement for the current GC'd closures - it has a completely different meaning that can be exactly what you need in particular situations, not only for performance-related issues. As an example, consider this code:
void delegate()[] arr;
foreach(i; 0..5)
{
arr ~= () => writeln(i);
}
foreach(f; arr)
{
f();
}
This is going to print "4 4 4 4", which might be the desired behavior. In other occasions you want to create a closure that captures the outer context by value. Here's some example pseudocode:
void value_delegate()[] arr;
foreach(i; 0..5)
{
arr ~= [i]() => writeln(i);
}
foreach(f; arr)
{
f();
}
The pseudocode above would explicitly capture `i` by value and produce a `struct`-like closure. It would print "0 1 2 3".
|
May 21, 2017 Re: Value closures (no GC allocation) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Vittorio Romeo | Hi Vittorio, wonderful to see you here after C++Now! :) On 05/20/2017 09:08 PM, Vittorio Romeo wrote: >consider this code: > > void delegate()[] arr; > > foreach(i; 0..5) > { > arr ~= () => writeln(i); > } > > foreach(f; arr) > { > f(); > } > > This is going to print "4 4 4 4", which might be the desired behavior. It's a bug. :/ https://issues.dlang.org/show_bug.cgi?id=8621 Ali |
May 21, 2017 Re: Value closures (no GC allocation) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Vittorio Romeo | On Sunday, 21 May 2017 at 04:08:04 UTC, Vittorio Romeo wrote: > This exact statement applied to C++ before C++11, but the introduction of lambda expression significantly changed the way people write and think about C++. Sometimes syntactic sugar can have huge impact on a language. Oh absolutely, make no mistake, I would be FOR this addition. I like it and do think it would be worth it in a lot of places. But, using the struct stuff, we can add some artificial sweetener now: return bar(lambda!(x, q{ (int y) => x + y })); You pass the captures first, then a q{} string literal of the lambda. Here's the implementation of that lambda template: template lambda(Args...) { import std.conv; import std.range; import std.string; string evil() { // build the functor import std.meta; string code = "static struct anon {"; foreach(i; aliasSeqOf!(iota(0, Args.length-1))) code ~= "typeof(Args[" ~ to!string(i) ~ "]) " ~ Args[i].stringof ~ ";"; string func = Args[$-1]; auto idx = func.indexOf("=>"); if(idx == -1) throw new Exception("No => in lambda"); // or we could use one of the other styles auto args = func[0 .. idx]; auto bod = func[idx + 2 .. $]; code ~= "auto opCall(T...)" ~ args ~ "{ return " ~ bod ~ "; }"; code ~= "this(T...)(T t) { this.tupleof = t; };"; code ~= "}"; return code; } mixin(evil()); anon lambda() { anon a; // copy the values in a.tupleof = Args[0 .. $-1]; return a; } } Yes, the C++ syntax is still a bit better and can give MUCH nicer error messages, but for short things, this isn't bad. > foreach(i; 0..5) > { > arr ~= () => writeln(i); > } so that's actually a long standing bug, but it hasn't been fixed for a long time.... But to work with that, you can do a capture with a wrapper function: arr ~= ((i) => delegate() { writeln(i); })(i); So you define a new function that returns the delegate and pass the argument right there. This technique is common in Javascript. Or, of course, using the artificial sweetener above, you can do: arr ~= lambda!(i, q{ writeln(i); }); ...assuming the imports are correct to call that library function... so i'll grant the artificial sweetener can leave a bitter aftertaste. (This is a good case to use in a feature request as to why the string mixin trick isn't actually a great replacement!) |
May 21, 2017 Re: Value closures (no GC allocation) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Adam D. Ruppe | On Sunday, 21 May 2017 at 18:17:57 UTC, Adam D. Ruppe wrote:
> But, using the struct stuff, we can add some artificial sweetener now:
>
> return bar(lambda!(x, q{ (int y) => x + y }));
>
> You pass the captures first, then a q{} string literal of the lambda. Here's the implementation of that lambda template:
>
> template lambda(Args...) {
>// ...
>
> anon lambda() {
> anon a;
> // copy the values in
> a.tupleof = Args[0 .. $-1];
> return a;
> }
> }
Looks cool, but it'd still want a GC closure, won't it?
|
May 21, 2017 Re: Value closures (no GC allocation) | ||||
---|---|---|---|---|
| ||||
Posted in reply to Stanislav Blinov | On Sunday, 21 May 2017 at 19:11:46 UTC, Stanislav Blinov wrote:
> Looks cool, but it'd still want a GC closure, won't it?
No, it just generates the same struct you'd write manually. That should work fine with @nogc now.
|
Copyright © 1999-2021 by the D Language Foundation