ruk·si

Monads

Updated at 2013-05-12 23:25

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