Thread overview
A currying function
Jun 18, 2012
bearophile
Jun 19, 2012
Artur Skawina
Jun 21, 2012
Philippe Sigaud
Jun 21, 2012
bearophile
Jun 21, 2012
David Nadlinger
Jun 21, 2012
David Nadlinger
Jun 21, 2012
Artur Skawina
Jun 22, 2012
Philippe Sigaud
June 18, 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
June 19, 2012
On 06/18/12 22:24, bearophile wrote:
> 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

> 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.
[...]
> I think this code induces the creation of heap allocated closures, so probably a more efficient version is needed:

I'm not really sure when you'd want to use this in D.
But, just to be able to say "Real Programmers don't use mixins": :)

   static struct curry(alias F) {
      import std.traits;
      ParameterTypeTuple!F args;
      template opCall1(size_t N=0) {
         auto ref opCall1(ParameterTypeTuple!F[N] a) {
            auto p = (cast(Unqual!(typeof(a))*)&args[N]);
            *p = a;
            static if (N==args.length-1)
               return F(args);
            else
               return &opCall1!(N+1);
         }
      }
      alias opCall1!() opCall;
      static curry opCall() { curry c; return c; }
   }
   //...
   double foo(immutable int x, in float y, short z=5) pure nothrow {
      return x * 2 + y * 3 + y * 5;
   }

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


The empty parens could be omitted, but you'd need a compiler with full property support for that (and of course would have to wrap this in a function). No heap allocations, the resulting code could be better though.

> So this version doesn't handle ref arguments.

Yeah, this one doesn't either; iterating and checking every arg would probably be necessary.

artur
June 21, 2012
On Tue, Jun 19, 2012 at 2:52 PM, Artur Skawina <art.08.09@gmail.com> wrote:

> I'm not really sure when you'd want to use this in D.

As for Haskell. For example, with a range of ranges and you want to take the first 100 elements of all subranges:

alias Curry!take ctake;

auto target = [[0,1,2,...], [...], ];
auto first100s = map!(ctake(100))(target);

And so on. When mapping and filtering ranges, I need currying from time to time.


> But, just to be able to say "Real Programmers don't use mixins": :)

I find Bearophile version quite nice. I did something equivalent a few
years ago, with string mixins also. Of course, the a => b syntax did
not exist then.
I
June 21, 2012
Philippe Sigaud:

> alias Curry!take ctake;
>
> auto target = [[0,1,2,...], [...], ];
> auto first100s = map!(ctake(100))(target);

The first argument of std.range.take is the range, this is handy for chaining with UFCS.

I think currently curry!take doesn't work because take is not a function (but maybe this is fixable).

Bye,
bearophile
June 21, 2012
On Thursday, 21 June 2012 at 19:04:32 UTC, Philippe Sigaud wrote:
> On Tue, Jun 19, 2012 at 2:52 PM, Artur Skawina
>> But, just to be able to say "Real Programmers don't use mixins": :)
>
> I find Bearophile version quite nice. I did something equivalent a few
> years ago, with string mixins also. […]

Using string mixins as done here isn't just no »real programmers« thing to do, it has very substantial problems.

The problem with bearophile's code is that it uses T.stringof to refer to the parameter, but in the real world – where the template is not defined in the same module as it is used from – this will not work, as the (non-builtin) argument types will simply not be in scope (the implementation couldn't possibly import all the modules the user could ever want to use types from, even ignoring Voldemort types and the likes for the moment).

I see this mistake – __traits(identifier, …) has just the same problem, by the way – being made so frequently even by experienced D programmers (IIRC dranges contains a few instances of it as well) that I wonder if there is anything we could do about it design-wise. At least, adding a big warning on how to _not_ use stringof/__traits(identifier, …) to the language docs seems like a good idea to me.

Coming back to this particular case, the issue is very easy to fix: just emit ParameterTypeTuple!F[i] instead of T.stringof from the mixin generating code.

As far as ref is concerned, I'm not aware of a universal way to handle it in the general case – I usually end up manually looking ref-ness up to generate code accordingly (see ParameterStorageClassTuple).

David
June 21, 2012
On Thursday, 21 June 2012 at 19:04:32 UTC, Philippe Sigaud wrote:
> As for Haskell. For example, with a range of ranges and you want to
> take the first 100 elements of all subranges:
>
> alias Curry!take ctake;
>
> auto target = [[0,1,2,...], [...], ];
> auto first100s = map!(ctake(100))(target);

To me, it seems like what you want here conceptually is partial application – it just happens to be equivalent to currying because the function has only two parameters.

I personally use partial application all the time, but never found myself in the need for currying so far. But maybe that's just because I don't have significant experience in Haskell and the likes.

David
June 21, 2012
On 06/21/12 21:04, Philippe Sigaud wrote:
> On Tue, Jun 19, 2012 at 2:52 PM, Artur Skawina <art.08.09@gmail.com> wrote:
> 
>> I'm not really sure when you'd want to use this in D.
> 
> As for Haskell. For example, with a range of ranges and you want to take the first 100 elements of all subranges:
> 
> alias Curry!take ctake;
> 
> auto target = [[0,1,2,...], [...], ];
> auto first100s = map!(ctake(100))(target);
> 
> And so on. When mapping and filtering ranges, I need currying from time to time.

Sure, but partial application would be enough for cases like this one.

   auto partappe(alias F, A...)(auto ref A a) {
      auto ref f(ParameterTypeTuple!F[0..$-A.length] b) { return F(b, a); }
      return &f;
   }

   int n = 2;
   auto target = [[0,1,2], [3,4,5], [6,7,8]];
   auto takeN = partappe!(take!(int[]))(n);
   auto firstN = map!takeN(target);

I wonder if there's a case for "real" currying in 'D'; still can't think
of one, but maybe that's just because it's not how i would usually code it.

artur
June 22, 2012
On Fri, Jun 22, 2012 at 12:12 AM, Artur Skawina <art.08.09@gmail.com> wrote:

> Sure, but partial application would be enough for cases like this one.
>
> I wonder if there's a case for "real" currying in 'D'; still can't think of one, but maybe that's just because it's not how i would usually code it.

Ah yes, you're right of course.