Disfunctional RSS

Where misquotes, misinformation, and misspellings occur daily.

Archive

May
12th
Sat
permalink

Everybody loves monads

I made a monad library for javascript. I’m hosting it here https://github.com/DrBoolean/MonadsJs. **This was a monad library based on the one for clojure since we weren’t using types in js. Since then I’ve updated the library to use objects as types so while the examples here still have the same outcome, they don’t work the same way anymore and the api is different. A great way to learn about monads is to watch Bob Martin’s talk. Let’s look at a few built in ones first. I wouldn’t worry about the mechanics so much, but rather the functionality you gain with these puppies.

MaybeM

Here’s the maybeM monad:
	
maybeM = defMonad({
  mResult: id,
  mBind: function(mv, f) {
    return mv ? f(mv) : null;
  }
})
Let’s see what it can do… Let’s pretend we’re in the admin section of some sexy backbone meteor ecommerce app. If we wanted to print the name of a customer attached to a cart we could define a function like this:

var getCustomerName = compose('.name', '.customer', '.order');

// and the calling code:
var cart = { order: { customer: { name : "Bob Jones" } } };
getCustomerName(cart); // "Bob Jones"
Yes yes, demeter wept, I know. But every once and a while you need a function like this. But what if there is no customer attached yet. Or order even?
	
var cart = { };
getCustomerName(cart); // TypeError: Cannot read property 'customer' of undefined
We’d have to do some crazy crap like this:

var getCustomerName = function(cart) {
  if(cart.order && cart.order.customer) return cart.order.customer.name;
}
Yuck! MaybeM to the rescue!
	
var getCustomerName = compose(liftM(maybeM, compose('.name','.customer')), '.order');

// calling...
var cart = { };
getCustomerName(cart); // null
What we’ve done is made a new function on the fly with liftM(maybeM, compose(‘.name’,’.customer’)) in the middle of our compose chain. The function will not run if order is null! “Look ma, no null checks”. So if we don’t care about the result we say it’s maybe there. In technical terms, we’ve attached a context to our value (attached a maybe context to our cart) and we “lifted” our function to work with that kind of value. We could have used the alternate monad syntax of:

var getCustomerName = maybeM(function(cart){
  order <- cart.order
  return order.customer.name;
});

Same deal. What’s cool is you can do a liftM(maybeM, myFragileFunction) where ever you want. You can define a function like that or use it when you’re calling it. That makes working with null easy. Let’s look at a different example.

ListM

	
listM = defMonad({
  mResult: function(x){
    return [x];
  },
  mBind: function(mv, f) { 
    return flatten(map(f, mv));
  }
});
The list monad is a little funky to use at first, but it is crazy useful. It’s just maps inside of maps. Keeping with our ecomm example, we’d like to display a dropdown of all the variations of each shirt and size. Let’s do that the hideous way first.
	
var shirts = ['"Mr. Bean" Shirt', '"I do what the voices in my head tell me" Shirt'];
var sizes = ["SM", "LG", "XL", "XXL"];

var makeOptions = function(){
  var result = [];
  for(var x=0;x
Calling it, we get something like:
	
[ '"Mr. Bean" ShirtSM',
  '"Mr. Bean" ShirtLG',
  '"Mr. Bean" ShirtXL',
  '"Mr. Bean" ShirtXXL',
  '"I do what the voices in my head tell me" ShirtSM',
  '"I do what the voices in my head tell me" ShirtLG',
  '"I do what the voices in my head tell me" ShirtXL',
  '"I do what the voices in my head tell me" ShirtXXL' ]
Let’s do it the sexy way:
	
var makeOptions = liftM(listM, "+");

makeOptions(shirts, sizes);
Wow, right?! Let’s take a moment to imagine the horror of adding colors in the first example. In our monadic one, nothing changes. We just call it with a third list:
	
var colors = ["red", "blue", "green", "yellow"];

makeOptions(shirts, sizes, colors);
Bitchin. You may decide to use that to create a list of every permutation and sort it to find the best options. You may also use it like this next example. Our ecomm site is in dire need of some stats because stats are fun to program. We want what products were purchased from a given set of users.

var users = [{orders: [{items: [{name: "product A"}, {name: "product B"}]}]}, {orders: [{items: [{name: "product C"}]}]}];

var getStats = listM(function(users) {
  u <- users
  o <- u.orders
  i <- o.items
  return i.name;
});

getStats(users); // [ 'product A', 'product B', 'product C' ]
It may be hard to see from my contrived example, but each line is it’s own map inside the line above that’s map. If you’re familiar with list comprehensions it works just like that. Let’s write that out by hand just to see.
	
var getStats = function(users) {
  return map(function(u){
    map(function(o){
      map(function(i) {
        return i.name;
      }, o.items);
    }, u.orders);
  }, users);
}
Now that we’ve seen some built in ones. Let’s see how to make a monad.

DotM

If we have our data type “dots” and we want to work with them as if they were numbers we can can define a dot monad
	
dotM = defMonad({
  mResult: compose(join(""), repeat(".")),
  mBind: function(d, f)  {
    return f(d.length);
  }
});
The monad has two functions mResult and mBind. The mResult is first so let’s talk about that first.

compose(join(""), repeat("."))
The repeat function usually takes two arguments: the thing to repeat and a number of times to repeat it. Here it is being partially applied with “.” so it’s just waiting for it’s number and it will return an array of .’s After that, we join the array and get a string of dots. Easy breezy. For the the mBind we make a function that first takes it’s dots, then it’s function and calls the function on the length of the dots. That effectively converts the dots into numbers for our function to use. So let’s see it in action.
	

var divideDots = dotM(function(da, db) {
  a <- da
  b <- db
  return a / b;
});

divideDots("....", "..");  // 2
The left arrow kind of “pulls” the number value out of the dot value. Then we just divide knowing that a and b are just numbers. You can also “lift” a normal function that doesn’t take dots into the dot monad. So divide works like a normal function, but we lift it and viola! Divides dots.
	
var divide = function(a,b) {
  return a / b;
}

var divideDots = liftM(dotM, divide);

divideDots("....", "..");  // 2
blog comments powered by Disqus