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