dipping into wu.js: autoCurry

It’s my pleasure to welcome our first guest blogger: Nick Fitzgerald is the author of the excellent wu.js “a lazy functional programming library”. It’s an inspiring resource with a lot of really original touches and very nicely written. Take it away Nick….

One of my favorite functions in my newly released wu.js library is wu.autoCurry, a function that takes a function and returns (you guessed it) a curry-able version of that function.

I am going to assume you are familiar with currying from here on out. If you aren’t yet, check out the Wikipedia article on currying for a general overview, and Angus’ piece on currying in Javascript for a detailed explanation in our current language.

Some languages, notably Haskell, automatically provide currying to the user. If you don’t pass all the arguments to a function, it is implied that a new function that will take the rest is returned. In Javascript, we don’t have that luxury. We are forced to write explicit, boilerplate code every time we want to curry a function.

Take, for example, giving each link on a page a single message to alert whenever it is clicked. Without currying it might look like this:

var elements = document.getElementsByTagName("a"),

messages = ["hey", "hiya", "whoa", "don't leave me",
            "that tickles", "ouch", "hee hee"],

alertMe = function (message, event) {
    alert(event.target + ": " + message);
    event.preventDefault();
};

for (var i = 0; i < elements.length; i++) {
    // Must force msg to be a local variable or else the closures
    // created by our anonymous function will all refer to the same msg
    // variable.
    (function (msg) {
        elements[i].addEventListener("click", function (event) {
            alertMe(msg, event);
        }, false);
    }(messages[i % messages.length]));
}

When we add currying in the mix, the event handler becomes much cleaner. We lose two extraneous function expressions and the msg variable:

var elements = document.getElementsByTagName("a"),

messages = ["hey", "hiya", "whoa", "don't leave me",
            "that tickles", "ouch", "hee hee"],

alertMe = function (message, event) {
    alert(event.target + ": " + message);
    event.preventDefault();
};

for (var i = 0; i < elements.length; i++) {
    elements[i].addEventListener("click",
                                 wu.curry(alertMe, messages[i % messages.length]),
                                 false);
}

This version is clearly cleaner than the first, but something still feels off. Its the explicit call to curry. If our alertMe function knew when to curry itself, the rest of our code would be a lot cleaner. We wouldn’t need to call curry ourselves.

var alertMe = function (message, event) {
    if (arguments.length === 0) {
        return alertMe;
    }
    else if (arguments.length === 1) {
        return function (event) {
            alert(event.target + ": " + message);
            event.preventDefault();
        };
    }
    else {
        alert(event.target + ": " + message);
        event.preventDefault();
    }
};

While alertMe handles currying for us now, there is code duplication, and it is mess to maintain. On top of that, if we want the same currying behavior from another function, we have to make a second (or third (or fourth (or fifth (or …)))) big mess checking arguments.length.

This is where wu.autoCurry comes in. It turns out we can generalize this pattern as long as we know how many arguments our function needs before it can perform its duties. Fortunately, every Javascript function has a length property! The length property is the number of parameters that are explicitly
defined in the function definition.

(function () {}).length
// 0
(function (a) {}).length
// 1
(function (a, b, c, d) {}).length
// 4

We can now re-write our code as follows:

var elements = document.getElementsByTagName("a"),

messages = ["hey", "hiya", "whoa", "don't leave me",
            "that tickles", "ouch", "hee hee"],

alertMe = wu.autoCurry(function (message, event) {
    alert(event.target + ": " + message);
    event.preventDefault();
});

for (var i = 0; i < elements.length; i++) {
    elements[i].addEventListener("click",
                                 alertMe(messages[i % messages.length]),
                                 false);
}

wu.autoCurry allows us to make sure that all arguments are in place before we attempt to apply them to the function.

But sometimes arguments are optional. It is common to have either functions that take n + m variables where n is constant and m can be anything from 1 to 20, or functions that take n arguments where the last couple are optional.

function foo(a, b /*, and one or more parameters */) {
    var params = Array.prototype.slice.call(arguments, 2);
    // ...
}

function bar(baz, bang, quux, blurg) {
    blurg = blurg || this;
    // ...
}

In both of these cases, the length property will not correctly reflect the number of parameters the function requires. At minimum foo requires three while foo.length is only two, and bar.length is 4 even though it is perfectly fine to only supply three arguments. wu.autoCurry(foo) will attempt to evaluate too early, while wu.autoCurry(bar) will require that last argument before attempting evaluation, even though it could evaluate earlier.

The solution is to tell wu.autoCurry the minimum number of arguments it should receive before applying them to the function.

foo = wu.autoCurry(foo, 3);
bar = wu.autoCurry(bar, 3);

wu.autoCurry is available in my wu.js library, or you can use the following, equivalent definition:

var autoCurry = (function () {

    var toArray = function toArray(arr, from) {
        return Array.prototype.slice.call(arr, from || 0);
    },

    curry = function curry(fn /* variadic number of args */) {
        var args = toArray(arguments, 1);
        return function curried() {
            return fn.apply(this, args.concat(toArray(arguments)));
        };
    };

    return function autoCurry(fn, numArgs) {
        numArgs = numArgs || fn.length;
        return function autoCurried() {
            if (arguments.length < numArgs) {
                return numArgs - arguments.length > 0 ?
                    autoCurry(curry.apply(this, [fn].concat(toArray(arguments))),
                              numArgs - arguments.length) :
                    curry.apply(this, [fn].concat(toArray(arguments)));
            }
            else {
                return fn.apply(this, arguments);
            }
        };
    };

}());

Questions and feedback are very welcome. Add comments here and Nick will reply

6 thoughts on “dipping into wu.js: autoCurry

Leave a comment