Currying is the process of taking a function that accepts some number of arguments, then creating a new version of that function that can accept an "incomplete" argument list. When given too few arguments, the curried function returns a new function that accepts the remaining arguments. For example, a curried version of adding might look like this:

  function addCurried(a, b) {
    if (typeof b !== "undefined")
        return a + b;
    else
        return function(c) { return a + c };
}

If addCurried(7) is called, it will return a function that accepts one argument as input and returns the result of adding 7 to that argument. This is a nifty trick if you've got, for example, some function that takes an element id and some values and does something with them, because you can create an element-specific function by giving just the id to the curried general function.

However, this isn't exactly currying. The specific definition of currying requires that a curried function accept only one argument at a time and keep returning new curried functions until it has enough arguments to execute. So if a function took three arguments, the curried function would need to take one, then another one, then another one. This is very useful, but let's see if we can create a more flexible general approach.

We'd like to create a function, curry(f, n, args ...) that takes a function as input and an integer representing the maximum number of arguments it can accept. We'd like to be able to pass it an arbitrary number of starting arguments as a shorthand, i.e. curry(f, n, 1, 2, 3) should be equivalent to curry(f, n)(1)(2)(3). We'd also like the intermediate steps to take any number of arguments as a shorthand, i.e. curry(f, n)(1, 2, 3) should be equivalent to those forms as well. The first step to this process, therefore, is to get an array of the arguments passed to the function for analysis. This can be done with the arguments object implicitly in the scope of all functions.

The problem is that arguments is an object, not an array. It does however support index-lookup, i.e. arguments[0] will give us the first argument passed to the function. Luckily, the Array prototype includes a .slice() method that returns a sub-array of an array based on a starting index, ending index, and a step. By using the .call() method, .slice() can be used on non-Array objects that behave like Arrays, such as our arguments object. In our incomplete currying function, this looks as follows:

  function curry(f, n) {
    var args = Array.prototype.slice.call(arguments, 0)
}

This is similar to arguments.slice(0) which means "make a new array using the elements of arguments starting at index 0 and continuing to the end one element at a time", but the above would not function because arguments is not an array. The .call method helps us get around this issue. Because we'll probably be dealing with quite a few array operations on the arguments object for this code, let's move that out into a simple helper function:

  function argsArray(argsObject) {
    return Array.prototype.slice.call(argsObject, 0);
}

function curry(f, n) {
    var args = argsArray(arguments);
}

Now we've got the tools we'll need to query our passed arguments. First of all, the simplest thing we'll need to check is if we've been passed n arguments yet. Note that the args array will include f and n as it's first two elements, so our condition will look like this:

  function curry(f, n) {
    var args = argsArray(arguments);
    if (n === args.length - 2) {
        // stuff
    }
}

If we've got all the arguments we need, then we need to find some way to call f with those arguments. This can be done with the .apply() member function of all Function instances. Roughly speaking, f.apply(a, [1, 2, 3]) is equivalent to f.call(a, 1, 2, 3) where a is the function's this variable's value. We can pass in the args array to the .apply function, but we'll need to trim out the first two values of the array, f and n, beforehand. This can be done with .slice(), making our final code for applying the function look like this:

  function curry(f, n) {
    var args = argsArray(arguments);
    if (n === args.length - 2) {
        return f.apply(undefined, args.slice(2));
    }
}

So we've covered the creation of the array of arguments and dealing with the case where we've got all the arguments we need to apply the function. Now we're just left with the tricky bit: returning a new curried function that handles the remaining arguments. We can handle this by noting a curious property of our definition of curry:

  curry(f, n, 1, 2)(3, 4) == curry(f, n, 1, 2, 3, 4);

Therefore, we can handle our new curried function with recursion. that is we can define curry(f, n, 1, 2) to return a function that evaluates to curry(f, n, 1, 2, args ...). This can be done by creating an anonymous function that references our args array and returns the result of .apply()-ing curry to our args array concatenated with an array of whatever arguments it's passed. Using the .concat() method of Array, our curry function would be:

  function curry(f, n) {
    var args = argsArray(arguments);
    if (n === args.length - 2) {
        f.apply(undefined, args.slice(2));
    }
    else {
        return function() {
            return curry.apply(undefined, args.concat(arguments));
        };
    }
}

And voila, we now have fairly flexible function currying. Here's some example code using our new function:

  function sum4(a, b, c, d) {
    return a + b + c + d;
}

alert(curry(sum4, 4, 10, 20)(1, 2));

And a cleaner version of our currying function with unnecessary braces and such removed would look like this:

  function curry(f, n) {
    var args = argsArray(arguments);
    if (n === args.length - 2)
        return f.apply(undefined, args.slice(2));
    return function() {
        return curry.apply(undefined, args.concat(argsArray(arguments)));
    };
}

As a final aside for those interested, the curried functions returned by curry are also closures, they hold on to references outside their scope. That's how our returned anonymous function can reference the args variable inside the definition of curry. Also note that the recursive call to curry introduces a new args variable that shadows the old one, so it's curried functions won't clash or overwrite earlier ones, letting you re-use the returned curried function multiple times to get differently curried "child" functions. Given how little it takes to create such a powerful tool, it's fair to say that functional programming is quite insane.

UPDATE

As commenter Awesomen3ss pointed out, functions have a property .length that tells you the length of their argument list. This can be used to modify curry to not need that value passed to it and have it default to f.length:

  function curry(f, n) {
    var args = argsArray(arguments);
    if (typeof n === 'undefined')
        args[1] = f.length;
    if (n === args.length - 2)
        return f.apply(undefined, args.slice(2));
    return function() {
        return curry.apply(undefined, args.concat(argsArray(arguments)));
    };
}

This helps avoid issues where you might supply an incorrect value of n to curry and end up with diffifcult-to-debug behavior.


10,612 4 21