ÆFLASH

Monads

After my last article, I've done some more research on what monads are. They seem to be one of the last core concepts of functional programming that is still a bit elusive to me. This is a good introductory video that finally made things start to click for me. Also, this article titled You Could Have Invented Monads made me realize that they really are not that complicated.

So what is a monad?

The simplest definition of a monad is "a container for computation" -- not very helpful. Monads apparently come from category theory in mathematics, and were introduced a way to contain side effects in a pure functional language (such as Haskell). If you're writing in a pure functional style -- absolutely no side effects, no mutable state -- your programs can't be very useful -- they can't do anything except provide a return value. With monads, you could use them to wrap these useful side effects (such as reading from a file, writing to console, handling socket input), and still maintain a pure functional "core".

A more useful definition of a monad is a system such that:

  • There is a class of function that takes some sort of value and returns a monad-wrapped result. f = (a) → M b
  • There exists a "bind" function that takes a wrapped value, and a function that returns a wrapped value, and returns a wrapped value. This is a mouthful. The notation looks something like ((M b), ((b) → M c) → M c. This allows you to compose two monad functions together like so: ((a) → M b) >>= ((b) → M c) where >>= is the bind operator in this notation.
  • The bind operator is associative, e.g. (f >>= g) >>= h is equivalent to f >> (g >>= h), where f, g, and h are these special monad functions (of type (a) → M b).
  • There exists a "unit" function that can be inserted into a chain of binds and have no effect on the end result. f >>= u equals u >>= f equals f.

This is a lot of notation, I'd recommend watching that first video to make things make more sense. One key point is that the definition of what M b actually equals is incredibly broad. It could be an object, a function, or a specific function signature. (Correct me in the comments if I'm wrong here.)

An Async Monad in Javascript

So what would a monad actually look like in Javascript? Can we actually do anything useful with them? Let's define our monadic type as a function that expects a single arg: a node-style callback. That's it.

function fooM (callback) {
    //... something happens here
}

fooM(function (err/*, results*/) {
    //... you can do stuff with results here
});

Any function that conforms to this signature would be a part of our monadic system. This means you can "lift" any node-style async function to be monadic through partial application.

function liftAsync(asyncFn/*, args...*/) {
  var args = _.rest(arguments);
  return function (callback) {
    asyncFn.apply(this, args.concat([callback]));
  };
}

/* example */

readFileM = liftAsync(fs.readFile, "./filename.json", "utf8");

/* or */

readFileM = fs.readFile.bind(fs, "./filename.json", "utf8");

This liftAsync function satisfies condition 1 above -- a function that takes something in and returns a monad-wrapped result. Now let's define the "bind" operation.

function bindM(fM, g) {
  return function (callback) {
    fM(function (err/*, results*/) {
      if (err) {return callback(err); }
      var results = _.rest(arguments)
      g.apply(this, results.concat([callback]));
    });
  };
}

/* example */

function asyncParse = function (text, callback) {/*...*/}

var readAndParseM = bindM(readFileM, asyncParse);

readAndParseM(function (err, data) {
  // parsed data from filename.json is here
});

(I call the function bindM to distinguish it from Function.bind. Same term, different operation.) It basically takes in a result, and a continuation, and specifies how to tie the two together. In the example, calling readAndParseM(callback) would be equivalent to:

fs.readFile("./filename.json", "utf8", function (err, text) {
  asyncParse(text, callback);
});

Condition 2 from the list is satisfied.

I'm going to gloss over point 3 a bit, but it's pretty easy to see that if you introduced a 3rd function uploadFields(data, callback) {}, these two snippets would be equivalent:

var parseAndUpload = function (text, callback) {
  return bindM(
    liftAsync(asyncParse, text),
    uploadFields
  )(callback);
}

bindM(readAndParseM, uploadFields);

/* equals */

bindM(readFileM, parseAndUpload);

Note that parseAndUpload is not a monad-wrapped function since it takes more than the callback argument. This is needed since we need to capture the value of data in a closure. Binding is not supposed to take in two monads, but a monad and a function that can be converted to a monad.

The "unit" function would be pretty simple:

function unitM(/*...args*/) {
  var args = _.toArray(arguments);
  return function (callback) {
    callback.apply(this, [null].concat(args));
  }
}

It just passes though what was passed in to it. You could easily see how binding this to any function, before or after, would have no effects on the result. Condition 4 satisfied.

So what?

So we have just defined a series of convoluted functions that allow us to tie together async functions. What use is it? It allows us to easily do higher-order operations with any function that expects a node-style callback.

We could use the unit function to make our example more consistent. We then could do:

var readFileM = bindM(unitM("./filename.txt", "utf8"), fs.readFile);

var readAndParseM = bindM(readFileM, asyncParse);

var readAndParseAndUploadM = bindM(readAndParseM, uploadFields);

We can also define an composeAsync function with bind:

function composeAsync(f, g){
  return function (/*...args, callback*/) {
    var args = _.toArray(arguments),
      callback = args.pop();
    return bindM(liftAsync.apply(null, [f].concat(args)), g)(callback);
  }
}

var readAndParseAndUploadM = bindM(
  readFileM,
  composeAsync(asyncParse, uploadFields)
);

Pretty cool. Our async functions become lego pieces we can combine together. It becomes tedious to progressively bind functions together, though. We could just reduce an array of operations:

[
  unitM("./filename.txt", "utf8"),
  fs.readFile,
  asyncParse,
  uploadFields
].reduce(bindM, unit())(function (err) { /* ... */ });

...or define a helper:

function doM(functions, callback) {
  functions.reduce(bindM, unit())(callback);
}

doM([
  unitM("./filename.txt", "utf8"),
  fs.readFile,
  asyncParse,
  uploadFields
], callback);

You may notice that the signature of doM is equivalent to async.waterfall. We have just recreated it in a monadic way! Calling our async functions is done in a purely functional manner, completely separated from the state each individual function may create.

This is but one of many possible monads in javascript -- the possibilities literally are endless. It's all in how you define your type signature, and your bind and unit functions. They don't always have to be asynchronous, but they work best when they wrap some modicum of external state change.

In my last article, I said that promises were also async monads, but object oriented. (i.e. the monad-wrapped value M b is an actual object.) It's pretty clear when you think about it. promisify(asyncFn) is simiar to liftAsync(asyncFn). promise.then() becomes the "bind" operator, since promisify(fn1).then(fn2).then(fn3).then(done, error) is equivalent to a non-OO when(when(when(promisify(fn1), fn2), fn3), done, error) that looks a lot like our bindM operator above. Same thing, different style.

code javascript functional programming monads async callbacks