I've been thinking a lot lately about functional programming, and I thought it might be kind of fun to walk through the process of writing a curry function.

For the uninitiated, currying refers to the process of taking a function with n arguments and transforming it into n functions that each take a single argument. It essentially creates a chain of partially applied functions that eventually resolves with a value.

Here's a basic example of how you'd use it:

function volume( l, w, h ) {  
  return l * w * h;
}

var curried = curry( volume );

curried( 1 )( 2 )( 3 ); // 6  
Disclaimer

This post assumes basic familiarity with closures and higher-order functions, as well as stuff like Function#apply(). If you're not comfortable with those concepts, you might want to brush up before reading further.

Writing our curry function

The first thing you'll notice is that curry expects a function as its argument, so we'll start there.

function curry( fn ) {

}

Next, we need to know how many arguments our function expects (called its "arity"). Otherwise, we won't know when to stop returning new functions and give back a value instead.

We can tell how many arguments a function expects by accessing its length property.

function curry( fn ) {  
  var arity = fn.length;
}

From there, things get a little bit trickier.

Essentially, every time a curried function is called, we add any new arguments to an array that's saved in a closure. If the number of arguments in that array is equal to the number of arguments that our original function expects, then we call it. Otherwise, we return a new function.

To do that, we need (1) a closure that can retain that list of arguments and (2) a function that can check the total number of arguments and either return another partially applied function or the return value of the original function with all of the arguments applied.

I usually do this with an immediately invoked function called resolver.

function curry( fn ) {  
  var arity = fn.length;

  return (function resolver() {

  }());
}

Now, the first thing we need to do in resolver is make a copy of any arguments it received. We'll do that by creating a variable called memory that uses Array#slice to make a copy of the arguments object.

function curry( fn ) {  
  var arity = fn.length;

  return (function resolver() {
    var memory = Array.prototype.slice.call( arguments );
  }());
}

Next, resolver needs to return a function. This is what the outside world sees when it calls a curried function.

function curry( fn ) {  
  var arity = fn.length;

  return (function resolver() {
    var memory = Array.prototype.slice.call( arguments );
    return function() {

    };
  }());
}

Since this internal function is the one that ends up actually being called, it needs to accept arguments. But it also needs to add those to any arguments that might be stored in memory. So first, we'll make a copy of memory by calling slice() on it.

function curry( fn ) {  
  var arity = fn.length;

  return (function resolver() {
    var memory = Array.prototype.slice.call( arguments );
    return function() {
      var local = memory.slice();
    };
  }());
}

Now, lets add our new arguments by using Array#push.

function curry( fn ) {  
  var arity = fn.length;

  return (function resolver() {
    var memory = Array.prototype.slice.call( arguments );
    return function() {
      var local = memory.slice();
      Array.prototype.push.apply( local, arguments );
    };
  }());
}

Good. Now we have a new array containing all the arguments we've received so far in this chain of partially applied functions.

The last thing to do is to compare the length of arguments we've received with the arity of our curried function. If the lengths match, we'll call the original function. If not, we'll use resolver to return yet another function that has all of our current arguments stored in memory.

function curry( fn ) {  
  var arity = fn.length;

  return (function resolver() {
    var memory = Array.prototype.slice.call( arguments );
    return function() {
      var local = memory.slice(), next;
      Array.prototype.push.apply( local, arguments );
      next = local.length >= arity ? fn : resolver;
      return next.apply( null, local );
    };
  }());
}

This can be a little bit difficult to wrap your head around, so let's take it step by step in an example.

function volume( l, w, h ) {  
  return l * w * h;
}

var curried = curry( volume );  

Okay, so curried is the result of passing volume into our curry function.

If you look back, what's happening here is:

  1. We store the arity of volume, which is 3.
  2. We immediately invoke resolver with no arguments, which means that for now, its memory array is empty.
  3. resolver returns an anonymous function.

Still with me? Now let's call our curried function and pass in a length.

function volume( l, w, h ) {  
  return l * w * h;
}

var curried = curry( volume );  
var length = curried( 2 );  

Again, here are the steps:

  1. What we actually called here was the anonymous function being returned by resolver.
  2. We made a copy of memory (which was empty) and called it local.
  3. We added our argument (2) to the local array.
  4. Since the length of local is less than the arity of volume, we call resolver again with the list of arguments we have so far. That creates a new closure with a new memory array, which contains our first argument of 2.
  5. Finally, resolver returns a new function that has access to an outer closure with our new memory array.

So what we get back is that inner anonymous function again. But this time, it has access to a memory array that isn't empty. It has our first argument (a 2) inside of it.

If we call our length function, the process repeats.

function volume( l, w, h ) {  
  return l * w * h;
}

var curried = curry( volume );  
var length = curried( 2 );  
var lengthAndWidth = length( 3 );  
  1. Again, what we actually called was the anonymous function being returned by resolver.
  2. This time, resolver had been primed with some previous arguments. So we make a copy of that array [ 2 ].
  3. We add our new argument, 3, to the local array.
  4. Since the length of local is still less than the arity of volume, we call resolver again with the list of arguments we have so far – and that returns a new function.

Now it's time to call our lengthAndWidth function and get back a value.

function volume( l, w, h ) {  
  return l * w * h;
}

var curried = curry( volume );  
var length = curried( 2 );  
var lengthAndWidth = length( 3 );

console.log( lengthAndWidth( 4 ) ); // 24  

This time, the steps are a little bit different at the end:

  1. Once again, what we actually called was the anonymous function being returned by resolver.
  2. This time, resolver had been primed with two previous arguments. So we make a copy of that array [ 2, 3 ].
  3. We add our new argument, 4, to the local array.
  4. Now the length of local is 3, which is the arity of volume. So instead of returning a new function, we return the result of calling volume with all of the arguments we've been saving up, and that gives us a value of 24.
Wrapping up

Admittedly, I have yet to find a super compelling use-case for currying in my day-to-day work. But I still think that going through the process of writing functions like this is a great way to improve your understanding of functional programming, and it helps reinforce concepts like closures and first-class functions.

By the way, if you like nerdy JavaScript things and live in the Boston area, I'm hiring at Project Decibel. Shoot me an email.