ianhenderson.org

XML

journal » currying in javascript

What is Currying?

Ok, let’s go back a bit. Or rather, down. Into the foundations of your programming knowledge. You know what a function is, right?


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

    f(1,2,3); // => 6

Ah, yes. We’ve seen this before. Takes some things in. Mashes it all together and spits something out. But there’s another way to look at these functions. What if we take just one argument at a time? Watch what happens…


    function g(a) {
        return function (b) {
            return function (c) {
                a+b+c;
            };
        };
    }

    g(1)(2)(3); // => 6

Do you see it? The new power we’ve been granted?


    add_1_2 = g(1)(2);

    add_1_2(3); // => 6
    add_1_2(10); // => 13

Here, f and g are obsessive number-collectors. Gotta catch ‘em all, and all that. But, as you probably know, not all number-collectors are alike. f, still a two-year-old at heart, wants his numbers now—or else he’ll throw an exceptional tantrum. g, on the other hand, has patience. “Someday,” she thinks, “I’ll find him. The perfect number. No matter how long I have to wait.”

So what does this all have to do with currying? Well, g is the curried one. Now you know. Curried functions are patient.

Curry for Everyone

What to Do

This is all very nice, but too much curry is apt to give us carpal tunnel if we don’t watch out. Too much typing. And what if it’s somebody else’s function? How do we curry that? What we want is this:


    g = curry(f);

The plain, boring function comes in. It comes out looking twenty years younger. At least.

Simple. Concise. Stunning. Now we make it run.

How to Do It

First, we must teach functions patience. How to take just a couple arguments at a time without freaking out. So let’s take the hurried Function.apply and slow it down a bit. Let’s call it Function.partially_apply.

partially_apply will have the same two arguments as the regular apply: a “this” for the function and the argument list. But instead of applying the function, it will return another function. This function applies the first function with the arguments given to partially_apply plus the arguments it itself was given. That may have sounded confusing—no worries; let’s go straight to the code. We’ll sort out the meaning afterwards.


    Function.prototype.partially_apply = function (orig_this, saved_args)
    {
        var this_function = this;
        return function () {
            var arg_array = new Array();
            cat(arg_array, saved_args);
            cat(arg_array, arguments);
            return this_function.apply(orig_this, arg_array);
        }
    }

    function cat(array, args)
    {
        for (var i=0; i<args.length; i++) {
            arr.push(args[i]);
        }
    }

Never mind cat; he’s there because in some browsers, the argument list isn’t actually an array. So we have to copy it over manually. Just think of cat as smushing the two lists together. Here’s the function as a recipe:

  1. keep track of what function we’re in
  2. make a new function to return; when this function is called:
    1. look at the arguments we called it with
    2. put these on the end of the arguments we already got
    3. call the function with these arguments

So when we call the resulting function, two things happen. First, it adds the arguments it got before with the arguments it’s being given. Then it calls the original function. This isn’t currying yet, but we’re getting there.


    add_1_2 = f.partially_apply(this, [1,2]);
    add_1_2(3); // => 6

    add_1 = f.partially_apply(this, [1]);
    add_1(2,3); // => 6
    add_1_2 = add_1(2); // BANG! Everything comes crashing down.

The problem here is that we can only go through the cycle once. We get a hold of one number, and we’re back to our old ways, impatient and hasty. We need to get it to keep applying until all the arguments are used up. And then give us the answer. How? Take a look.


    function curry(orig_fun)
    {
        return function () {
            if (arguments.length < orig_fun.length) {
                return arguments.callee.partially_apply(this, arguments);
            } else {
                return orig_fun.apply(this, arguments);
            }
        };
    }

There it is. curry, in all its resplendent glory. So how’s it work?

First, we make a function. This function’s job is to decide whether it’s found all the arguments yet. If it has, it calls our original function right away. But if it hasn’t, we fall back on our trusty partially_apply function. But what’s that argument.callee business all about? argument.callee is actually a reference to the function we’re in right now. So we’re partially applying inside the very function that is being partially applied? “Shouldn’t this cause an infinite loop?” I hear you asking. But partially_apply doesn’t do much the first time around. It’s the function it returns that actually applies. And what does it end up applying? Our decision-making function again!

Let’s look at an example. Remember, f is a function that adds three numbers together. Let’s curry it, and call it g.


    g = curry(f);

Now we tell g to do something. Then trace what happens.


    add_1 = g(1);

arguments.length is less than orig_fun.length, so we go knock on partially_apply’s door. It returns a function that calls arguments.callee with the before and after argument lists squished together. arguments.callee is just g, though. And [1] is the before argument list. So, in code, that says:


    add_1 = function () {
        g.apply(this, [1] + arguments);
    };

Which is precisely what we want. And this can go on and on.


    add_1(2) = g(1,2) = g(1)(2);

Identical! Amazing.

A Happy Ending, and Other Trivialities

So now you can curry! How does it feel? Warm and fuzzy? Prickly? Shy, yet strong? Read it again if you want. Nobody’s stopping you. I’ll wait for you here.

Ok, so that’s that. Hope you had fun. I know I did.

P.S.: You can get a curry.js for your own webpages right here.

P.P.S.: There is another guide to javascript-currying on the web. It goes more in-depth in certain areas. I dunno. You might like it.