We need to have a way to say "convert this nested function into a struct"

Atila Neves via Digitalmars-d digitalmars-d at puremagic.com
Thu Oct 29 08:10:49 PDT 2015


On Wednesday, 28 October 2015 at 11:42:08 UTC, John Colvin wrote:
> On Saturday, 6 June 2015 at 12:49:37 UTC, Atila Neves wrote:
>> On Saturday, 6 June 2015 at 06:59:26 UTC, Jonathan M Davis 
>> wrote:
>>> On Saturday, 6 June 2015 at 06:16:17 UTC, Andrei Alexandrescu 
>>> wrote:
>>>> [...]
>>>
>>> Some of us were discussing this at dconf. Essentially, we 
>>> need a way to create a functor similar to how C++ lambdas do. 
>>> The most straightforward way would involve string mixins, and 
>>> you'd do something like
>>>
>>> auto f = makeFunctor!"function code here"(arguments);
>>> auto result = range.algorithm!f();
>>>
>>> but that's not terribly pretty. Atila seemed to have figured 
>>> out how we could do it with std.functional.partial, but I was 
>>> too tired at the time to quite understand what his proposal 
>>> was. So, we may have something better there. Ideally, we'd be 
>>> able to just give a lambda, but that would put us right back 
>>> in the problem of a delegate being allocated unnecessarily 
>>> (though IIRC, Atila's suggestion somehow worked with lambdas 
>>> and partial without allocating; I wish that I could remember 
>>> what he proposed). But while it may or not be as pretty as 
>>> we'd like, I think that it's at last _possible_ for us to 
>>> have a shorthand for creating a functor by just providing the 
>>> function's body and arguments that hold the values for its 
>>> members. I'm certainly not against finding a language way to 
>>> make it prettier though, since I'm not sure how clean we can 
>>> really do it without language help.
>>>
>>> That being said, we really should find a way to make it so 
>>> that lambda's don't turn into delegates unless they really 
>>> need to. In many, many cases, they should be plenty efficient 
>>> without having to force the issue with functors, but they 
>>> aren't, because we allocate for them unnecessarily. I don't 
>>> know how easy it'll be though for the compiler devs to figure 
>>> out how to optimize that, since sometimes you _do_ need to 
>>> allocate a closure.
>>>
>>> But having a shorthand way to create functors would 
>>> definitely allow us to force the issue where necessary. And 
>>> from what Liran was saying at dconf, that alone would make it 
>>> possible for them to use a lot of Phobos that they can't 
>>> right now. I suspect that unnecessary closures are actually 
>>> the main reason that we have GC allocation problems with 
>>> Phobos, since most algorithms just don't explicitly involve 
>>> allocation unless they're doing array-specific stuff.
>>>
>>> - Jonathan M Davis
>>
>> I remember the conversation but not really what I said. 
>> However, I just wrote this:
>>
>>
>> import std.stdio;
>> import std.algorithm;
>> import std.range;
>> import std.conv;
>> import std.traits;
>> import std.exception;
>>
>>
>> auto functorPartial(alias F, T)(T arg) {
>>     struct Functor {
>>
>>         T arg;
>>
>>         this(T args) { //because of opCall
>>             this.arg = arg;
>>         }
>>
>>         auto opCall(U...)(U rest) {
>>             return F(arg, rest);
>>         }
>>     }
>>
>>     return Functor(arg);
>> }
>>
>> int adder(int i, int j) {
>>     return i + j;
>> }
>>
>> void main(string[] args) {
>>
>>     enforce(args.length > 1, "An argument must be passed in");
>>
>>     auto arg = args[1].to!int; //to prove it's at runtime
>>     auto adderPartial = functorPartial!adder(arg); //runtime 
>> value
>>     writeln("adder result: ", adderPartial(4));
>>
>>     //"subtracter"? "subtractor"? who cares
>>     auto subtracterPartial = functorPartial!((a, b) => a - 
>> b)(arg);
>>     writeln("subtracter partial: ", subtracterPartial(4));
>>
>> }
>
> Unfortunately this doesn't solve the problem in general with 
> @nogc. When passing one of these functors to e.g. 
> std.algorithm.map, there is no way to avoid the reference to 
> the current scope. The challenge is to implement a (correct, 
> see https://issues.dlang.org/show_bug.cgi?id=14982) @nogc 
> version of this function without rewriting map:
>
> auto foo(int a)
> {
>     return iota(10).map!(x => x + a);
> }
>
> I don't think it can be done without language changes.
>
> I wonder what could be done if we could get inspect and 
> manipulate context pointers in code...

And why is rewriting map off the table? The code below works. The 
only difference with respect to C++ is no syntax for variable 
capture.

import std.stdio: writeln;
import std.conv: to;
import std.range: isInputRange, iota;


void main(string[] args) {
     int a = args[1].to!int;
     writeln(foo(a));
}

auto foo(int i) @nogc @safe pure nothrow {
     return iota(i).map(functionPartial!((a, b) => a + b)(i));
}

auto map(R, F)(R range, F func) if(isInputRange!R) {
     static struct Result {
         R range;
         F func;

         auto front() {
             return func(range.front);
         }

         void popFront() {
             range.popFront;
         }

         bool empty() const {
             return range.empty;
         }
     }

     return Result(range, func);
}

auto functionPartial(alias F, T)(T arg) {

     static struct Function {

         T arg;

         this(T arg) { //because of opCall
             this.arg = arg;
         }

         auto opCall(U...)(U rest) const {
             return F(arg, rest);
         }
     }

     return Function(arg);
}

Atila


More information about the Digitalmars-d mailing list