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