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.