Monads and Objects
If you went back to 1990 and asked a random programmer what an object was (as in OOP) what would he say? I bet he'd say something like "I don't know. I want to learn them but I don't know where to start." If you told him "Well, they're easy. Each object has a class. The class defines methods. The object encapsulates state . . .", then his eyes would glaze over. He receives a maelstrom of concepts and their relationships. In the end, he would learn nothing. It actually took a long time for OO to become the norm.
This is analagous to what is happening currently with monads. People want to learn, they get some high-concept explanation, they are unsatisfied. People try to explain. They really do. I still remember when I did not get monads, so I have a lot of sympathy.
Perhaps, like with OOP, it will take a generation to become understandable to the mainstream.
The best way I can think of to teach monads to a programmer is jQuery. You have to know jQuery to understand this example. And frankly, if you don't know jQuery, it is easier to go learn it and come back than to go read a monad tutorial.
Here's a (not-so-)secret: jQuery objects are a monad. When you do
$('div')
, you get an object which "contains" all of the div
elements in the document. There are many methods on the jQuery object
which modify the set of elements contained in the object and return it.
That's what makes it a monad: the methods return a value of the same
type.
Without the jQuery object, you have to follow (and
re-follow—potentially repeating code) the logic of
collections.
The jQuery object controls the logic by which operations on the
contained set of elements gets executed. You call a method, it changes
all of the elements. If there is no element, nothing happens. If there
is one element, it alone is changed. If there are many elements, they
are all changed. And you don't need to know. If you call
$('<div />')
, even though you are constructing an object with only one
element, jQuery internally turns it into a list. jQuery is making sure
you don't have to know if you have 0, 1, or many elements. And this
turns out to be a useful abstraction.
This logic is simple. You write this code all the time when you are dealing with collections of objects. But you have to write this all the time, over and over. And you have to remember to write it. The jQuery object does that for you.
That is the job of the monad. It gives you a single place to express the logic for access (in this case, a kind of collection logic). Now we are going to make a monad and show how we can write this logic in only one place.
To make a monad, you actually need two things. One is typically called
return
, but in OOP it's the constructor. We know that the jQuery
constructor is the $
function.
The second thing you need is a way to chain methods. All you need is
to return a value constructed with that constructor. Then you can chain.
In Haskell, this is called bind
.
But I promised that monads got rid of repetition. If all of your jQuery methods have to contain this collection logic and return logic, how does that get rid of repetition? Well, if you did it the way it's done in Haskell, you'd get rid of the repetition.
Let's do it that way. Let's define a method on the jQuery called
bind
.
jQuery.prototype.bind = function (f) {
return $(f(this.get()));
};
We construct a new jQuery object with the elements transformed by f
.
So I can write:
function filter_divs(els) {
var arr = [];
var i;
for (i = 0; i < els.length; i++)
if (els[i].tagName === "div") arr.push(els[i]);
return arr;
}
$(".x").bind(filter_divs).addClass("hello");
Let's take it further. We can bind the function and store it in the prototype:
jQuery.prototype.bind_def = function (name, f) {
jQuery.prototype[name] = function () {
return $(f(this.get()));
};
};
Now we can call it like a method.
$.bind_def("filter_divs", filter_divs);
$(".x").filter_divs().addClass("hello");
So, now we can chain. We only have to write the logic once (in bind
or bind_def
). And we have a constructor. That's a monad.
Now, imagine other bits of logic you could build into a method chain.
What happens if a method returns null
? If you call a method on it, it
will throw an exception. You can capture the null check in a monad.
That is called the Maybe Monad.
There are other useful monads, but they all are simply structured ways of chaining by giving controlled access to the internals of the object.
Thanks to Douglas Crockford for some of these ideas.