Auto-currying in JavaScript
In this post, we explore auto-currying in JavaScript.
This is the fourth article in the series titled Curry Functions in JavaScript. It follows the previous article named Currying Existing Functions with JavaScript, where we mentioned that curried functions can be of various complexities, such as auto-curried unary functions, variadic ones, infinitely curried functions, etc.
This post will cover auto-currying with a purely unary implementation.
Auto-currying, what the Heck ?
Auto-currying is an advanced currying implementation technique. It is an in-built feature in Haskell, where a multary function has to be curried by the compiler, because all functions are unary to begin with. So, a multary function always gets auto-curried in Haskell.
In JavaScript, though, functions are not restricted to be unary. We're currying stuff for the heck of it, because it's exciting and fun. We don't have auto-currying in JavaScript, because we literally don't need it. And just for the record, we might not call currying in JavaScript "currying" in the actual sense. By the mid of the next article, we'll find out we're just fooling around.
We want to implement currying ourselves. Obviously, it won't be auto-currying in the sense that our functions should be curried automatically by the compiler; rather that we are making the transformation with our own code by moving it to runtime. That is, instead of manually defining a currying function for each passed-in multary function, we are automating the task of transformation for that function with a wrapper and a couple of helpers.
We can do this by making use of plain JavaScript function composition along with recursion. And in the later posts we will see that we can achieve the same transformation with native JavaScript methods like Function.prototype.call
, Function.prototype.apply
, Function.prototype.bind
as well.
Automating the Currying Process
In the previous post, Currying Existing Functions with JavaScript, we saw that we used a wrapper function that acts as an accumulator of arguments by leveraging nested closures. The accumulator function plays the central role in currying. We need one in every currying transformation.
However, in auto-currying we don't use a nested functions system. Instead, we use recursion. We track the number of arguments passed, compare it to the multary function's arity, and use the comparison to decide whether we should keep accepting more arguments by returning the accumulator recursively. Once we have enough arguments, we terminate the accumulator and return a call to the to-be-curried function.
The Plan
The key idea is to allow accepting only one argument at a time - as unarity is the essence of currying. Then we go ahead and store the arguments in an array, whose copy should be always accessible from the current execution context of the recursive stack.
Example
In the following example, we define the accumulator to accept only one argument at a time:
function curry(f) {
function curried(args) {
if (args.length >= f.length) return f(...args);
return accumulator;
function accumulator(a) {
return curried([...args, a]);
};
};
return curried([]);
};
Notice that we are declaring accumulator()
inside the curried()
helper function to give it access to the args
array so that an updated args
can be passed on to successive recursive calls.
Notice also that we are using args
in the parameter of curry()
as an array and not as a list spread from the array: ...args
. And we pass an empty array to the first call on curried()
.
And now, we want to curry our createMessage()
function with curry()
at runtime and use it:
function createMessage(greeting, name, message) {
return `${greeting}, ${name}! ${message}`;
};
const curriedCreateMessage = curry(createMessage);
console.log(curriedCreateMessage('Hi')('Haskell')('Whadup?')); // Hi, Haskell! Whadup?
console.log(curriedCreateMessage('Hi', 'Hello')('Haskell', 'Hasikell')('Whadup?', `What's up`));
// Hi, Haskell! Whadup?
Notice the second log
above ignores additional arguments and returns the same Hi, Haskell! Whadup?
string as the first, despite being passed two arguments at each call.
We could have written the curry()
function like this:
function curry(f) {
return function curried(...args) {
if (args.length >= f.length) return f(...args);
return accumulator;
function accumulator(a) {
return curried(...args, a);
};
};
};
But there is a problem with this for returning all-out unary functions. We'll come to that in the next article on variadic currying. But what's the problem?
Feel free to share your thoughts in the comments.
Oldest comments (2)
It should be pointed out that this won't work as expected with functions that have optional parameters, since
function.length
doesn't include them.well pointed