digitalmars.D - A currying function
- bearophile (98/98) Jun 18 2012 I think std.functional.curry is badly named because I think it's
- Artur Skawina (38/51) Jun 19 2012 I'm not really sure when you'd want to use this in D.
- Philippe Sigaud (12/14) Jun 21 2012 As for Haskell. For example, with a range of ranges and you want to
- bearophile (7/10) Jun 21 2012 The first argument of std.range.take is the range, this is handy
- David Nadlinger (25/31) Jun 21 2012 Using string mixins as done here isn't just no »real
- David Nadlinger (9/15) Jun 21 2012 To me, it seems like what you want here conceptually is partial
- Artur Skawina (15/29) Jun 21 2012 Sure, but partial application would be enough for cases like this one.
- Philippe Sigaud (2/5) Jun 21 2012 Ah yes, you're right of course.
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
Jun 18 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 42So 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
Jun 19 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
Jun 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
Jun 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 SkawinaUsing 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). DavidBut, 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. […]
Jun 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
Jun 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: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. arturI'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.
Jun 21 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.
Jun 21 2012