A currying function

bearophile bearophileHUGS at lycos.com
Mon Jun 18 13:24:59 PDT 2012


I think std.functional.curry is badly named because I think it's 
a "partial application" (I suggest to rename it).


In Haskell and Scala currying is built-in, this is Haskell, shell 
(ghci):

Prelude> let foo x y z = x * 2 + y * 3 + y * 5
Prelude> let foo1 = foo 5
Prelude> let foo2 = foo1 4
Prelude> foo2 3
42


Through Reddit I've found a curry() in C++11:
https://github.com/LeszekSwirski/cpp-curry


So I've written a D version of it below, not tested much. I have 
not translated the C++11 version. This was quite easy to write, 
and much simpler and shorter than the C++11 version (but maybe 
this misses something, this is just a first draft). This kind of 
stuff was quite longer to do in D1, but now with CTFE on strings 
ops, defining one or more inner static functions that build a 
string at compile-time, it's quite easy. Now I don't need lot of 
templates to do this.

There are two alternative designs, curry1 seems a bit more 
elegant, but if you want to use it in-place it forces you to add 
a () at the beginning, that's not nice and intuitive. So probably 
curry2 is better.


import std.traits: isCallable, ParameterTypeTuple;
import std.string: join;
import std.conv: xformat;

@property curry1(alias F)() if (isCallable!F) {
     static string genChain() {
         string[] chain;
         string[] args;
         foreach (i, T; ParameterTypeTuple!F) {
             chain ~= xformat("(%s x%d)", T.stringof, i);
             args ~= xformat("x%d", i);
         }
         return chain.join(" => ") ~ " => F(" ~ args.join(", ") ~ 
");";
     }
     mixin("return " ~ genChain());
}

auto curry2(F)(F f) if (isCallable!F) {
     static string genChain() {
         string[] chain;
         string[] args;
         foreach (i, T; ParameterTypeTuple!F) {
             chain ~= xformat("(%s x%d)", T.stringof, i);
             args ~= xformat("x%d", i);
         }
         return chain.join(" => ") ~ " => f(" ~ args.join(", ") ~ 
");";
     }
     mixin("return " ~ genChain());
}

// default arguments are ignored
double foo(immutable int x, in float y, short z=5) pure nothrow {
     return x * 2 + y * 3 + y * 5;
}

void main() {
     import std.stdio;

     writeln(foo(5, 4, 3));

     auto cf = curry1!foo;
     auto c2 = cf(5)(4);
     writeln(c2(3));

     writeln(curry1!foo()(5)(4)(3));
     writeln(curry2(&foo)(5)(4)(3));
}



This is the code string generated by genChain() for the foo() 
function:

(immutable(int) x0) => (const(float) x1) => (short x2) => f(x0, 
x1, x2);


I think this code induces the creation of heap allocated 
closures, so probably a more efficient version is needed:

_D4test24__T5curryTPFNaNbyixfs...
L0: push EAX
     push EAX
     push EBX
     push 8
     call near ptr __d_allocmemory
     mov  EBX,EAX
     mov  EAX,0Ch[ESP]
     mov  [EBX],EAX
     fld  float ptr 014h[ESP]
     mov  EAX,EBX
     mov  EDX,offset FLAT:_D4test24__T5curryTPFNaNbyixf...
     fstp float ptr 4[EBX]
     add  ESP,4
     pop  EBX
     add  ESP,8
     ret  4


If the function is:

double bar(ref int x, ref double y) pure nothrow {
     x++;
     return x * 2 + y * 3;
}


it generates:

(int x0) => (double x1) => f(x0, x1);

So this version doesn't handle ref arguments.

Bye,
bearophile


More information about the Digitalmars-d mailing list