Monad syntax for JavaScript

Following on from my introduction to monads in JavaScript, and before I get into how they apply to asynchronous programming, I’d like to take a quick detour to improve the usability of the tools we’ve built up. Recall we have a function for composing functions:

var compose = function(f, g) {
  return function(x) { return f(g(x)) };
};

We have some ‘debuggable’ functions:

// sine :: Number -> (Number,String)
var sine = function(x) {
  return [Math.sin(x), 'sine was called.'];
};

// cube :: Number -> (Number,String)
var cube = function(x) {
  return [x * x * x, 'cube was called.'];
};

And finally we have the unit and bind functions for the ‘debuggable’ monad:

// unit :: Number -> (Number,String)
var unit = function(x) { return [x, ''] };

// bind :: (Number -> (Number,String)) -> ((Number,String) -> (Number,String))
var bind = function(f) {
  return function(tuple) {
    var x  = tuple[0],
        s  = tuple[1],
        fx = f(x),
        y  = fx[0],
        t  = fx[1];

    return [y, s + t];
  };
};

These let us compose our debuggable functions to create new debuggable functions:

var f = compose(bind(sine), bind(cube));
f(unit(3)) // -> [0.956, 'cube was called.sine was called.']

This is all well and good, but we should really be able to compute sin(x^3) as a one-liner. With the code we have, this expression would be:

bind(sine)(bind(cube)(unit(3)))

This is hardly convenient, especially when I tell you that the equivalent Haskell expression is:

return 3 >>= cube >>= sine

This reads like a pipeline: take 3, pass it through cube, then pass the result of that through sine. In Haskell, unit is called return, and bind is actually an operator (or infix function) called >>=. The operator >>= doesn’t just convert functions into composable form, it takes a monadic value – in our example, a (Number,String) tuple – and a debuggable function, and deals with unpacking the value from the monad, applying the function to it, and combining the result with the monad in a meaningful way.

Using our existing names, a direct translation to JavaScript of this would be:

bind( bind( unit(3), cube), sine)

Where the bind function now looks like this:

// bind :: (Number,String) -> (Number -> (Number,String)) -> (Number,String)
var bind = function(x, f) {   //  e.g. x = [3, ''], f = cube
  var y  = x[0],              //           3
      s  = x[1],              //           ''
      fy = f(y),              // cube(3) = [27, 'cube was called.']
      z  = fy[0],             //           27
      t  = fy[1];             //           'cube was called.'

  return [z, s + t];
};

This representation is clearer, but not quite as expressive as the Haskell version. Let’s go one step forward:

var y = pipe(unit(3), [cube, sine])

This seems reasonably close to Haskell’s version, and to go any further we’d really need some syntactic abstractions that JavaScript does not have. This pipe function is straightforward to implement: it takes a monadic value, and uses bind to pipe it through the list of debuggable functions:

// pipe :: (Number,String) -> [Number -> (Number,String)] -> (Number,String)
var pipe = function(x, functions) {
  for (var i = 0, n = functions.length; i < n; i++) {
    x = bind(x, functions[i]);
  }
  return x;
};

We can easily use this with anonymous inline functions without losing much expressiveness:

var z = pipe(unit(7), [ function(x) { return [x+1, 'inc.'] },
                        function(x) { return [2*x, 'double.'] },
                        function(x) { return [x-1, 'dec.'] }
                      ])

// z == [15, 'inc.double.dec.']

Here we’ve calculated (2 * (7 + 1)) -- 1 using fairly direct syntax, where the operations and the log messages are nicely separated. If you’ve done a lot of asynchronous programming, this will probably be starting to look somewhat familiar, and indeed I’ll be showing where this leads in the next article.

If you’ve enjoyed this article, you might enjoy my recently published book JavaScript Testing Recipes. It’s full of simple techniques for writing modular, maintainable JavaScript apps in the browser and on the server.