Reputation: 14519
Is it possible to write a template or similar that can be used to automatically curry functions in D? Manually writing out all the nested delegates is killing me.
Basically, for a function f
with e.g. 3 arguments, which can usually be called like f(a,b,c)
, I want it to be callable as f(a)(b)(c)
.
I know about std.functional.partial, but that's not what I want. I want to translate the function definition side, not the calling side.
I also know this is far from best practice, but I'm generating code, so bear with me.
Upvotes: 2
Views: 81
Reputation: 25595
Well, something along these lines should do the job:
template autocurry(alias what) {
import std.traits;
static if(Parameters!what.length)
auto autocurry(Parameters!what[0] arg) {
alias Remainder = Parameters!what[1 .. $];
auto dg = delegate(Remainder args) {
return what(arg, args);
};
static if(Remainder.length > 1)
return &autocurry!dg;
else
return dg;
}
else
alias autocurry = what;
}
int foo(int a, string b, float c) {
import std.stdio; writeln(a, " ", b, " ", c);
return 42;
}
string test() {
import std.stdio; writeln("called test");
return "no args";
}
void main() {
import std.stdio;
alias lol = autocurry!foo;
writeln(lol(30)("lol")(5.3));
auto partial = lol(20);
partial("wtf")(10.5);
alias t = autocurry!test;
writeln(t());
}
The idea there is pretty simple: generate the helper function based on the remaining arguments - if there are any, return the address of the helper as the delegate, otherwise, just return the delegate that calls the collected arguments. A little recursiveness handles 1+ arg cases, and the static if on the outside handles the 0 arg case by just returning the original function.
Language features to note:
eponymous templates. When a template has a member with the same name as the template (in this case, autocurry), it is automatically referenced when used.
tuple expansion. When I call what(arg, args)
, the args
one, being a built-in tuple, is automatically expanded to create the complete argument list.
the various auto returns here (the explicit auto autocurry
and the implicit delegate
keyword without specifying a return type) just forward whatever other random type the body happens to return.
In the main function, I did alias lol = autocurry!foo;
(I use lol as my placeholder name a lot, lol). You could also overload it at top level:
int foo(int a, string b, float c) {
import std.stdio; writeln(a, " ", b, " ", c);
return 42;
}
alias foo = autocurry!foo; // overloads the auto-curried foo with the original foo
And now you can use it directly, along side the original:
void main() {
foo(30)("lol")(5.3); // overload resolves to curried version
foo(40, "cool", 103.4); // overload resolves to original automatically
}
If you prefer a new name or the overload is up to you, either can work.
Note that each argument is liable to allocate some memory to store it for the next delegate. The GC will be responsible for cleaning that up.
Upvotes: 4