Monads
Monads exist to make Haskell people to feel smarter than everybody else.
This note is about monads. Before you can understand monads, you need to understand wrapped values, functors and applicatives.
Wrapped Value
We have a number value.
2
We can apply a function to that value
(+3) 2
-- => 5
Extend this by saying that the value can have context.
-- 2 + context
2c
Now if you apply function to this new wrapped value, result depends on the value and context.
-- `Maybe` data type defines two contexts, `Just` and `Nothing`.
data Maybe a = Nothing | Just a
Functor
Functor applies a function to a wrapped value.
When value is wrapped with a context, you cannot apply normal functions to it. You need a functor that knows how to handle that specific type of wrapped value.
Launch functor
Unwrap value
Apply function to unwrapped value creating new value
Wrap new value
Return wrapped new value
Applicative
Applicative applies a wrapped function to a wrapped value.
Applicative is a function wrapped in context that operates on context wrapped values.
Launch applicative
Unwrap function
Unwrap value
Apply unwrapped function to unwrapped value creating new value
Wrap new value
Return wrapped new value
Monads
Monads are a loophole in pure functional language function contract.
Pure functions always return same output for same input but they also closes over some state.
So calling a functions by passing in a function is always a call with unique parameter. Parameter function is first time in that particular state.
Monad is basically a control flow that utilizes that, functional if-else-statement.
Monad applies a function that returns a wrapped value, to a wrapped value.
Monad is programmable control flow.
// underscore.js
_.chain(value) -> <OPERATIONS> -> .value()
Example: JavaScript Monad
Monad is an object.
// Unit wraps an value to basic container that
// functions we are using can consume.
// Returns a monad object.
function unit(value) {};
// Returns a monad object.
function bind(monad, function) {};
Monads have three axioms.
// Relationship with bind and unit functions.
bind( unit(value), f ) ==== f(value)
bind( monad, unit ) ==== monad
// Composition.
bind( bind(monad, f), g )
====
bind( monad, function (value) {
return bind( f(value), g );
})
Object oriented approach axioms.
// Relationship with bind and unit functions.
unit(value).bind(f) ==== f(value)
monad.bind(unit) ==== monad
// Composition.
monad.bind(f).bind(g)
====
monad.bind(function (value) {
return f(value).bind(g);
});
Identity Monad
function MONAD() {
return function unit(value) {
// Create object with null as prototype.
var monad = Object.create(null);
monad.bind = function(func) {
return func(value);
};
return monad;
};
}
var identity = MONAD();
var monad = identity("Hello world.");
monad.bind(alert);
Maybe Monad
function MONAD(modifier) {
var prototype = Object.create(null);
function unit(value) {
var monad = Object.create(prototype);
monad.bind = function (func, args) {
var x = [value].concat(Array.prototype.slice.apply(args || []));
return func.apply(undefined, x);
};
if (typeof modifier === 'function') {
modifier(monad, value);
}
return monad;
}
return unit;
}
// Usage
var maybe = MONAD(function (monad, value) {
if ( value === null || value === undefined ) {
monad.isNull = true;
monad.bind = function () {
return monad;
};
}
});
var monad = maybe(null);
// Does not execute because it is null.
monad.bind(alert);
monad.isNull
// => true
What Is This Sorcery?
We have a function cube that gets a number and returns a number.
cube :: Number -> Number
If we want to include debug information, in case it is deeply nested.
cube :: Number -> (Number, String)
Then we need to make sure it is chainable so input and output parameter need to be the same.
cube :: (Number, String) -> (Number, String)
This gets annoying really fast. What happens when something changes? How about we just create our own variable type?
unit :: Number -> (Number, String)
Monads are a design pattern that says that whenever you have a class of functions that accept one type of thing and return another type of thing, there are two functions that can be applied across this class to make them composable: bind
and unit
.
bind
function transforms any function so that accepts the same type as it returns, making it composable.
unit
function wraps a value so it is accepted by the composable functions.
Sources
- Functors, Applicatives, And Monads In Pictures
- Monads and Gonads, Douglas Crockford