Gorm Casper31 July 2015

Inadvertently breaking composability

When you work with classes in JavaScript (that is, approximations of classes), everything tends to become a class. Have a look at this simple and contrived implementation of a recursive factorial function:

// MathUtil.js
var MathUtil = {
    fac: function (n) {
        if (n <= 1) return 1;

        return n * this.fac(n-1);
    }
}

return MathUtil;

Don’t do this. This is not a class. This is an object, with different types of values (presumably mostly functions) and there is no need to call this. The problem is that you are assuming that this is bound correctly. This is a big (and unnecessary) assumption to make, and really, it is putting the onus on the user for your own sloppy (or ignorant) implementation.

Let’s test it. A simple console will do.

MathUtil.fac(3);
// → 6

[1, 2, 3].map(MathUtil.fac);
// → Uncaught TypeError: this.fac is not a function

Sure. We can use it in a map in other ways. This is the most common way I see when doing code reviews:

[1, 2, 3].map(function (n) {
    return MathUtil.fac(n);
});
// → [1, 2, 6]

So much boilerplate! There’s also this next one. I like it more, because it clearly acknowledges the problem.

[1, 2, 3].map(MathUtil.fac.bind(MathUtil));
// → [1, 2, 6]

The solution here, is just to swap out this for MathUtil.

// MathUtil.js
var MathUtil = {
    fac: function (n) {
        if (n <= 1) return 1;

        return n * MathUtil.fac(n-1);
    }
}

return MathUtil;

That’s it. Problem solved.

[1, 2, 3].map(MathUtil.fac);
// → [1, 2, 6]

It is maybe not too surprising that these mistakes occur. Making code composable is difficult, and this is notoriously troublesome to work with in JavaScript, but this post is not about this, it is about constructing code to minimise surprises (that is, minimise knowledge required to use the code correctly). Here is another (rather infamous) example:

var a = [ 0,  1,  2,  3,  4,
          5,  6,  7,  8,  9,
         10, 11, 12, 13, 14,
         15, 16, 17, 18, 19];

a.map(parseInt)
// → [  0, NaN, NaN, NaN, NaN,
      NaN, NaN, NaN, NaN, NaN,
       10,  12,  14,  16,  18,
       20,  22,  24,  26,  28]

What happens here is that the second argument (the index) passed to the callback (parseInt) in the map is used as the radix from which to calculate the new number.

Let’s return to our factorial function for a second. It is recursive, so maybe we should structure it in such a way that takes advantage of the (soon to come) tail call optimization in browsers. Let’s give it a shot:

// MathUtil.js
var MathUtil = {
    fac: function (n, accu) {
        accu = arguments.length < 2 ? 1 : accu;

        if (n <= 1) return accu;

        return MathUtil.fac (n-1, n*accu);
    }
};

return MathUtil;

Looks great, doesn’t it? No! No it does not. Did you fall for it? I hope you didn’t. We introduced the very same problem that we eliminated earlier.

MathUtil.fac(1);             // → 1
MathUtil.fac(2);             // → 2
MathUtil.fac(3);             // → 6
[1, 2, 3].map(MathUtil.fac); // → [0, 2, 12]

The solution, is to use the closure provided to us when modularising the code.

// MathUtil.js
function recursiveFac (n, accu) {
    if (n <= 1) return accu;

    return recursiveFac(n-1, n*accu);
}

return {
    fac: function (n) {
        return recursiveFac(n, 1);
    }
};

Or, in case we’re in ES6 land:

// MathUtil.js
const recursiveFac = (n, accu) =>
    n <= 1 ? accu : recursiveFac(n-1, n*accu);

export const fac = n => recursiveFac(n, 1);

Good luck!