Drathier
Drathier

Reputation: 14519

Template for currying functions in D?

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

Answers (1)

Adam D. Ruppe
Adam D. Ruppe

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

Related Questions