Better Currying in Javascript
Whilst indulging in some purely functional PHP, I came across an improvement that can be made to our beloved Curry function.
Let’s say we have an id
entity function, defined as
follows:
var id = c(function(x) { return x; });
This takes an argument and spits it back out. If we pass it a function, we can call that function:
id(function(x) { return 'Hello ' + x; })('World');
However, we’ve hit the annoying chained-parentheses issue that our
curry function tried to avoid: foo(bar)(baz)
.
There’s a trick to working around this: we should only ever give a function as many arguments as it was defined to have.
Most implementations of currying will gather as many arguments as possible, then pass them all to the non-curried function they wrap, even if it’s more than the non-curried function requires. That leads to the above behaviour.
Instead, we should only be passing the required number of arguments; if we’re given more than that, we should keep hold of the rest until the non-curried function has returned. We can then pass these extra arguments to the return value, under the assumption that it’s a function (if that assumption’s wrong, it our caller’s fault for giving us too many arguments).
This allows us to use a simple, single set of parentheses as the standard interface for what could be quite convoluted stack gymnastics; we hide the implementation cruft :)
Here’s the improved Curry function (also using Function.apply
rather than eval
, since the
presence of eval
makes static
analysis impossible):
var c = curry = function(f) {
// This is our curry function. It will make f Curryable
var apply = function(args) {
// Helper function.
// Takes an array of arguments args, returns
// f(args[0], args[1], ..., args[args.length - 1])
var extra_args = args.slice(f.length);
var result = f.apply(null, args.slice(0, f.length));
.forEach(function(a) {
extra_args= result(a);
result ;
})return result;
;
}
var add_args = function(old_args) {
// Helper function.
// Takes an array of arguments we've been given so far,
// If they're enough for f then we run it.
if (old_args.length >= f.length) return apply(old_args);
// If not, we return a function to gather more.
return function() {
var new_args = [];
for (var i = 0; i < arguments.length; i++)
.push(arguments[i]);
new_argsreturn add_args(old_args.concat(new_args));
;
};
}// We kick things off by applying no arguments
return add_args([]);
; }
If we use this version of Currying, we can get rid of the chained-parentheses:
id(function(x) { return 'Hello ' + x; }, 'World');
var triple = c(function(x, y, z) { return x + y + z; });
id(triple, 'Hell', 'o Wor', 'ld');
Of course, we’re free to use chained parentheses if we want!
id(triple, 'Hell', 'o Wor', 'ld');
id(triple)('Hell', 'o Wor', 'ld');
id(triple, 'Hell')('o Wor', 'ld');
id(triple, 'Hell', 'o Wor')('ld');
id(triple)('Hell')('o Wor', 'ld');
id(triple)('Hell', 'o Wor')('ld');
id(triple, 'Hell')('o Wor')('ld');
id(triple)('Hell')('o Wor')('ld');
What do we lose by doing this? We must make all arguments explicit. However, I see this as good style anyway.
Happy hacking!