Currying in non-curried languages

There are a lot of currying-in-X tutorials floating around, for example I've written my own for Javascript and PHP.

Recently this one appeared on Hacker News and I got involved in the discussion, which made me think a bit harder about a peculiarity of my implementations which I've not seen in anyone else's.

My blog post on PHP discusses this peculiarity in the "Returning Functions" section. My Javascript implementation linked above was written before this discovery, but I've detailed it in this followup.

The idea with all implementations is that we make a 'curry' function such that the following hold:


// Curry a function
var curried = curry(function(a, b, c) { return [a, b, c]; });

// By the definition of currying
curried(x)(y)(z) == [x, y, z]

// For convenience and interoperability
curried(x, y, z) == curried(x)(y, z)
                 == curried(x, y)(z)
                 == curried(x)(y)(z)
                 == [x, y, z]

The difference in my implementations is what happens when we're given more arguments than the function's arity:


// Curry another function
var two_level = function(a, b, c) {
                  return function(d, e, f) {
                    return [a, b, c, d, e, f];
                  };
                };
var curried2 = curry(two_level);

// Most implementations pass all arguments once arity has been reached
curried2(u, v, w, x, y, z) == two_level(u, v, w, x, y, z)
                           == function(d, e, f) {
                                return [u, v, w, d, e, f];
                              };

// My implementations only pass the minimum number; the rest go to the return value
curried2(u, v, w, x, y, z) == two_level(u, v, w)(x, y, z)
                           == [u, v, w, x, y, z]

Two consequences of this are:

  • All function arities become constant (though overridable via wrappers)
  • Currying a manually-curried function (ie. a function which returns a function) will *uncurry* it, allowing both levels to be called as one!

My current implementations either pass all remaining arguments to the first return value (PHP) or pass one argument at a time to successive return values (Javascript; essentially "foldl ($) f args"). These work best when the returned functions are also curried, but there's no reason we can't curry them on-the-fly, or look up how many to supply and loop until we run out. We could even use such a loop to execute tail-calls while we're at it ;)

This uncurrying is effectively the same as the convenience functionality shown in the first code block, but extended to our return values as well as our arguments. In other words:

When we have too few values, we produce wrapper functions to accept the leftovers as their arguments.
When we have too many values, we consume wrapper functions by passing the leftovers as their arguments.

I'd love to hear people's opinions on this. Is my approach better, worse or complementary to the standard approach? Has this duality been noticed before? Is this just a reason not to use n-ary functions? ;)

Edit: In case you can't tell, my aim is to have expressions like "a(b, c, d, e, f, g, ...)" behave like a Haskell expression "a b c d e f g ...".