Jump to page: 1 24  
Page
Thread overview
Value closures (no GC allocation)
May 21, 2017
Vittorio Romeo
May 21, 2017
Adam D. Ruppe
May 21, 2017
Stanislav Blinov
May 21, 2017
Adam D. Ruppe
May 21, 2017
Vittorio Romeo
May 21, 2017
Ali Çehreli
May 21, 2017
Adam D. Ruppe
May 21, 2017
Stanislav Blinov
May 21, 2017
Adam D. Ruppe
May 21, 2017
Stanislav Blinov
May 21, 2017
Adam D. Ruppe
May 21, 2017
Stanislav Blinov
May 22, 2017
Vittorio Romeo
May 22, 2017
Stanislav Blinov
May 24, 2017
Vittorio Romeo
May 24, 2017
Stanislav Blinov
May 25, 2017
MysticZach
May 25, 2017
Stanislav Blinov
May 25, 2017
Mike Parker
May 25, 2017
Stanislav Blinov
May 25, 2017
Mike Parker
May 25, 2017
Stanislav Blinov
May 25, 2017
Mike Parker
May 25, 2017
Adam D. Ruppe
May 25, 2017
Stanislav Blinov
May 25, 2017
Adam D. Ruppe
May 25, 2017
Stanislav Blinov
May 29, 2017
MakersF
May 29, 2017
Adam D. Ruppe
May 21, 2017
Francesco Zoffoli
May 30, 2017
deadalnix
Jun 04, 2017
MysticZach
Jun 04, 2017
MakersF
May 21, 2017
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
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
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
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
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
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
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
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
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
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.
« First   ‹ Prev
1 2 3 4