Applicative functors in JavaScript

November 12, 2014

In category theory, an applicative builds on a functor to allow the application of a lifted function to a lifted value. Practically speaking, this allows a function of type F[A => B] to be converted into a function of type F[A] => F[B], for some type constructor F.

I like to picture it like this:

.--------------.         .-------------------------.
|  Category _  |         |      Category F[_]      |
|--------------|         |-------------------------|
| A            |         | F[A]                    |
| B            |         | F[B]                    |
| A => B       |         | F[A => B]               |
|              |         | |                       |
|              |         | `~~ ap ~~> F[A] => F[B] |
'--------------'         '-------------------------'

This can be read as follows:

  1. The category _ contains objects of type A, B, and A => B, where A => B is a function from A to B.
  2. The category F[_] contains objects of type F[A], F[B], and F[A => B].
  3. An applicative for F[_] converts F[A => B] into F[A] => F[B].

This is useful when we have a lifted function of type F[A => B] and we want to apply it to a value of type F[A], resulting in a value of type F[B].

Though JavaScript doesn't have functors, applicatives, or static types, we can still take advantage of this pattern in our data structures.

Option

In many functional languages, an Option is a box that might contain a value. It has two implementations: Some, which contains a value of a given type, and None, which does not contain a value.

We can implement these in JavaScript with a couple of constructor functions:

function some(x) {
  return {
      empty   : false
    , map     : function(f) { return some(f(x)); }
    , flatMap : function(f) { return f(x); }
    , ap      : function(a) { return a.map(x); }
    , show    : function()  { return 'some(' + x + ')'; }
  };
}

function none() {
  return {
      empty   : true
    , map     : function(f) { return this; }
    , flatMap : function(f) { return this; }
    , ap      : function(a) { return this; }
    , show    : function()  { return 'none()'; }
  };
}

If we imagine that Option represents the category of optional values, we can see that some and none are applicatives. Here's how it looks:

.--------------.         .-----------------------------------.
|  Category _  |         |        Category Option[_]         |
|--------------|         |-----------------------------------|
| A            |         | Option[A]                         |
| B            |         | Option[B]                         |
| A => B       |         | Option[A => B]                    |
|              |         | |                                 |
|              |         | `~~ ap ~~> Option[A] => Option[B] |
'--------------'         '-----------------------------------'

Let's take it for a spin.

var increment = function(x) { return 1 + x; };
var f = some(increment);
var w = some(41);

var a = f.ap(w);
console.log(a.show()); // some(42)

First, we define a unary function increment that adds one to its argument. Next, we lift it up into the Option category so that we can apply it with an optional argument. If this argument is some(x), its value is passed into the increment function, yielding some(x+1). If this argument is instead none(), then there is nothing to pass into the increment function, and none() is returned.

var multiply = function(x) { return function(y) { return x * y; }; };
var g = some(multiply);
var x = some(21);
var y = some(2);

var b = g.ap(x).ap(y);
console.log(b.show()); // some(42)

var z = none();
var c = g.ap(x).ap(z);
console.log(c.show()); // none()

In a more interesting example, multiply is a curried function of two arguments. We again lift it into the Option category, and this time apply it with two optional arguments x and y. If either argument is none(), the result will also be none(), otherwise it will be some(x*y).