web stats

Monads in plain JavaScript

So you want to know what these Monads thing is? Douglas Crockford in an interview said that if you understand them you lose the ability to explain them, and then went on to do a Monads & Gonads talk. In the talk he said you don’t have to understand category theory or Haskell and I happen to agree, but then he jumps in to some generic monad constructor that may be confusing at first. So what’s a monad in plain english?

Simply put it’s a wrapper around a value. There are also some laws that it should conform to, but more on that later.

As with any wrapper we need two methods, one to get and one to set the value. These are called “return” and “bind”. Let’s construct the “Maybe” monad – the easiest monad to get your head around:

var Maybe = function(value) {
this.value = value;
};

So we have a constructor, monads are something called a “functor” which basically is a “functional object” that allows you to move values between sets. In this case the maybe monad will allow us to use “undefined” in places which expect an actual value and we would see an error otherwise.

So let’s look at the return function:

Maybe.prototype.ret = function() {
return this.value;
};

Nice and easy, it gives us a method to get the value of the monad. Now comes the fun part, bind:

Maybe.prototype.bind = function(fn) {
if (this.value != null)
return fn(this.value);
return this.value;
};

So the bind function runs a given function with the value of the monad. In the case of the maybe monad it just skips running the function if the value doesn’t exist – and that’s it!

Now we should talk about “lift”. you’ll see that the bind will return whatever the function returns for the value. Well we want a monad as a return so you should be passing in functions that return a monad – but who wants to do that? instead we’ll just create a “lift” function that can take in a function that returns a normal value and changes it to a monad. pretty easy, it’d look something like this:

Maybe.lift = function(fn) {
return function(val) {
return new Maybe(fn(val));
};
};

so now we can do things like have an addOne function:

var addOne = function(val) {
return val + 1;
};

lift it:

var maybeAddOne = Maybe.lift(addOne);

then you can use it with bind! But what if we have a function that takes two monads? say we want to add two together?

Maybe.lift2 = function(fn) {
return function(M1, M2) {
return new Maybe(M1.bind(function(val1) {
return M2.bind(function(val2) {
return fn(val1, val2);
});
}));
};
};

This one’s a bit more complicated, but basically it just uses closures to get the values from the two monads before running it through the function, and because it’s a maybe monad it will just pass back undefined  so we can safely use undefined values without errors. You can try it out like this:

var add = function(a, b) {return a + b;};
m1 = new Maybe(1);
m2 = new Maybe(2);
m3 = new Maybe(undefined);

var liftM2Add = Maybe.lift2(add);

liftM2Add(m1, m2).ret(); //3
liftM2Add(m3, m2).ret(); //undefined
liftM2Add(m1, m3).ret(); //undefined

And that’s it. So to recap a monad is just a container. You can pass in a function to operate on it and get a returned value, or ask for it’s value. You’ve probably used monads in the past without knowing (like promises, you just send it a function to run on it’s value and it returns back itself – so it’s a lifted bind) and perhaps even created some.

Powered by WPeMatico