Skip to content

Instantly share code, notes, and snippets.

@RocketPuppy
Created March 6, 2014 04:23
Show Gist options
  • Save RocketPuppy/9382640 to your computer and use it in GitHub Desktop.
Save RocketPuppy/9382640 to your computer and use it in GitHub Desktop.
/*
* The Maybe monad type. All monads must respond to both `bind` and `return`
* functions. Here return is called `ret` as return is already a keyword. I've
* defined `bind` and `return` on the Maybe prototype so they can be used by
* the two subclasses `Just` and `Nothing`.
*/
function Maybe(){}
/*
* The bind function takes a single function as an argument. That function must
* take a value of the same type as is inside the Just and return a new
* instance of the Maybe class (either Just or Nothing).
* If bind is called on a Just, it applies the given function to the value in
* the just.
* If bind is called on a Nothing, it short circuits and returns Nothing.
*/
Maybe.prototype.bind = function(f){
if(this instanceof Just){
return f(this.val);
}
else if(this instanceof Nothing){
return Maybe.Nothing;
}
};
/*
* The ret(urn) function simply wraps a value in the default context of the
* monad. In this case, a Just.
*/
Maybe.prototype.ret = function(val){
return new Just(val);
};
/*
* For something to be a monad it must have more than just the bind and return
* functions. Those functions also need to abide by the monad laws. Here I'll
* prove that these do just that.
*
* First: Left-identity - (return x).bind(f) === f x
* 1. (Maybe.ret(x)).bind(f) === f x
* 2. (Just(x)).bind(f) === f x //Maybe.ret(x) simplifies to Just(x)
* 3. f x === f x //Just(x).bind(f) simplifies to f x
*
* Second: Right-identity - m.bind(return) === m
* Case 1: Nothing
* 1. Nothing.bind(Maybe.ret) === Nothing
* 2. Nothing === Nothing //Nothing.bind(Maybe.ret) simplifies to Nothing
* Case 2: Just(x)
* 1. (new Just(x)).bind(Maybe.ret) === Just(x)
* 2. Maybe.ret(x) === Just(x) //bind applies Maybe.ret to the inner value
* 3. Just(x) === Just(x) //Maybe.ret returns a new Just with the given value
*
* Third: Associativity - (m.bind(f)).bind(g) === m.bind( function(x){ f(x).bind(g) } )
* Case 1: Nothing
* 1. (Nothing.bind(f)).bind(g) === Nothing.bind( function(x){ f(x).bind(g) } )
* 2. Nothing.bind(g) === Nothing //the first binds simplify to Nothing
* 3. Nothing === Nothing //the last bind simplifies to Nothing
* Case 2: Just(y)
* 1. (Just(y).bind(f)).bind(g) === (Just(y)).bind( function(x){ f(x).bind(g) } )
* 2. (f(y)).bind(g) === (function(y){ f(y).bind(g) })(y) //the first binds simplify
* 3. f(y).bind(g) === f(y).bind(g) //reduce function and parens
*/
/*
* The Just subclass of Maybe. It contains a single value.
*/
function Just(val){
this.val = val;
}
Just.prototype = new Maybe();
/*
* The Nothing subclass of Maybe. It signifies Nothing.
*/
function Nothing(){}
Nothing.prototype = new Maybe();
Maybe.Nothing = new Nothing();
/*
* A possible use of the Maybe monad.
* Here we have a safe division operation. Used with bind, it will return
* Nothing if its argument is 0, short-circuiting the rest of the chain and
* preventing an exception.
*/
function safeDiv(num){
return function(denom){
if(denom != 0){
return new Just(num/denom);
}
else{
return Maybe.Nothing
}
}
}
/*
* Usage Examples
*/
onlyEven = function(x){
if(x % 2 === 0){
return new Just(x);
}
else{
return Maybe.Nothing;
}
};
onlyOdd = function(x){
if(x % 2 === 0){
return Maybe.Nothing;
}
else{
return new Just(x);
}
};
usage1 = function(x){return (new Just(x)).bind(function(x){ return new Just(x + 20)}).bind(function(x) {return new Just(x * 4)}).bind(safeDiv(62345)).bind(function(x){ return new Just(x - 4)})}
/*
* There exists a concept called "Lifting" which allows one to convert a
* function that isn't in a monad, to one that is. I'm not sure how to
* implement that in JS right now.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment