Skip to content

Instantly share code, notes, and snippets.

@Integralist
Last active March 6, 2016 18:02
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Integralist/a2c2fcdf565047910126 to your computer and use it in GitHub Desktop.
Save Integralist/a2c2fcdf565047910126 to your computer and use it in GitHub Desktop.
Monads in JavaScript and the power of composability
// The Monad design pattern functions
// i.e. compose, bind, unit, lift
// These are functions that are part of the design pattern's "interface"
// Their implementation can change (well, maybe not `compose`)
// but these function identifiers should be implemented
// as other devs will use those identifiers as a common language
// when discussing the Monad pattern
// compose = returns a function that takes a data structure (accepted data structure argument is passed to `g` and then result of that is passed to `f`)
// bind = returns a function that takes a data structure (function return value is same expected data structure type; function extracts values from original data structure and manipulates them before returning new data structure)
// unit = wraps the value provided in a data structure that is expected by other functions we'll be using
// lift = returns a function that accepts a value. The value is passed to the lifted function and that return value is passed to `unit`. It uses partial application to call `compose` with first argument pre-filled (in this case `unit`) and then function provided as argument to `lift` is passed as second argument to `compose` (returning a function that accepts a single value; accepted argument to that function is passed to `g` and then result of that is passed to `f`)
var compose = function(f, g) {
return function(x) {
return f(g(x));
};
};
var bind = function(f) {
return function(tuple) {
var x = tuple[0],
s = tuple[1],
fx = f(x),
y = fx[0],
t = fx[1];
return [y, s + t];
};
};
var unit = function(x) { return [x, ''] };
var lift = function(f) { return compose(unit, f) };
// User functions
var sine = function(x) { return [Math.sin(x), 'sine was called.'] };
var round = function(x) { return Math.round(x) };
var roundDebug = lift(round);
// See our example executed...
/*
compose(bind(roundDebug), bind(sine))
function (x) {
return f(g(x));
}
*/
var f = compose(bind(roundDebug), bind(sine));
console.log(
f(unit(27))
); // -> [1, 'sine was called.']
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Breakdown of each function's composability...
// [27, ""]
console.log(unit(27));
// f = function (x) { return f(g(x)) }
console.log(compose(bind(roundDebug), bind(sine)));
// [0.8414709848078963, "sine was called."]
console.log(sine(1));
// 1
console.log(round(1));
// function (x) { return f(g(x)) }
// i.e. function (x) { return unit(round(x)) }
console.log(lift(round));
// [1, ""]
// roundDebug was `lift`ed
console.log(roundDebug(1));
/*
Both functions below return (where `f` is the function binded)...
function (tuple) {
var x = tuple[0],
s = tuple[1],
fx = f(x),
y = fx[0],
t = fx[1];
return [y, s + t];
}
*/
console.log(bind(roundDebug));
console.log(bind(sine));
/*
function (x) { return f(g(x)) }
i.e.
function (x) {
return function (tuple) {
var x = tuple[0],
s = tuple[1],
fx = roundDebug(x),
y = fx[0],
t = fx[1];
return [y, s + t];
}(
function (tuple) {
var x = tuple[0],
s = tuple[1],
fx = sine(x),
y = fx[0],
t = fx[1];
return [y, s + t];
}(x)
)
}
*/
console.log(compose(bind(roundDebug), bind(sine)));
// f(unit(27)) == function ([27, ""]) { return f(g([27, ""])) }

All code sourced directly from: https://blog.jcoglan.com/2011/03/05/translation-from-haskell-to-javascript-of-selected-portions-of-the-best-introduction-to-monads-ive-ever-read/

  • compose, which converts two functions into a composed/nested set of functions
  • bind, which converts a debuggable function into a composable form
  • unit, which converts a simple value into the format required for debugging, by placing it in a container
  • lift, which converts a ‘simple’ function into a debuggable function

some functions

// These functions want to be composable but aren't because
// they return different data types to what was passed in.
// The functions are no longer "pure"
var sine = function(x) {
  return [Math.sin(x), 'sine was called.'];
};

var cube = function(x) {
  return [x * x * x, 'cube was called.'];
};

compose

// Takes two functions f and g and 
// returns another function that computes f(g(x))
var compose = function(f, g) {
  return function(x) {
    return f(g(x));
  };
};

bind

// Take a Number -> (Number,String) function and 
// return a (Number,String) -> (Number,String) function
var bind = function(f) {
  return function(tuple) {
    var x  = tuple[0],
        s  = tuple[1],
        fx = f(x),
        y  = fx[0],
        t  = fx[1];
    
    return [y, s + t];
  };
};

unit

// The role of unit is to take a value and wrap it in a basic container 
// that the functions we’re working with can consume. 
// For our debuggable functions, this just means 
// pairing the number with a blank string:
// unit :: Number -> (Number,String)
var unit = function(x) { return [x, ''] };

// This unit function also lets us convert any function into a debuggable one, 
// by converting its return value into an acceptable input for debuggable functions:

// round :: Number -> Number
var round = function(x) { return Math.round(x) };

// roundDebug :: Number -> (Number,String)
var roundDebug = function(x) { return unit(round(x)) };

// This type of conversion, from a 'plain' function to a debuggable one, 
// can be abstracted into a function we’ll call lift (see below)

lift

// lift :: (Number -> Number) -> (Number -> (Number,String))
var lift = function(f) {
  return function(x) {
    return unit(f(x));
  };
};

// or, more simply:
var lift = function(f) { return compose(unit, f) };

Example (using the above functions)

var sine = function(x) {
  return [Math.sin(x), 'sine was called.'];
};

var compose = function(f, g) {
  return function(x) {
    return f(g(x));
  };
};

var bind = function(f) {
  return function(tuple) {
    var x  = tuple[0],
        s  = tuple[1],
        fx = f(x),
        y  = fx[0],
        t  = fx[1];

    return [y, s + t];
  };
};

var unit = function(x) { return [x, ''] };

var lift = function(f) { return compose(unit, f) };

// Usage...
var round = function(x) { return Math.round(x) };
var roundDebug = lift(round);
var f = compose(bind(roundDebug), bind(sine));
f(unit(27)) // -> [1, 'sine was called.']

Monads?

The functions bind and unit describe a Monad.

So what is a monad? Well, it’s a design pattern. It 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:

  • There is a bind function that transforms any function so that accepts the same type as it returns, making it composable
  • There is a unit function that wraps a value in the type accepted by the composable functions.

It's a very useful design pattern to be aware of because it helps you spot accidental complexity: code that isn't dealing directly with the problem at hand, but which is dealing with glueing data types together. Being able to spot and extract such boilerplate can radically improve the clarity of your code.

DOM example

Suppose you have a function whose job is to take one DOM node and return all its children as an array; its function signature says that it takes a single HTMLElement and returns an array of HTMLElements.

// children :: HTMLElement -> [HTMLElement]
var children = function(node) {
  var children = node.childNodes, ary = [];
  for (var i = 0, n = children.length; i < n; i++) {
    ary[i] = children[i];
  }
  return ary;
};

var heading = document.getElementsByTagName('h3')[0];
children(heading)

To get the grandchildren we might manually write it like:

// grandchildren :: HTMLElement -> [HTMLElement]
var grandchildren = function(node) {
  var output = [], childs = children(node);
  for (var i = 0, n = childs.length; i < n; i++) {
    output = output.concat(children(childs[i]));
  }
  return output;
};

But that's not very functional. We need to take two steps to fix this: provide a bind function to turn children into composable form, and write a unit function to turn the initial input – the heading – into an acceptable type.

// unit :: a -> [a]
var unit = function(x) { return [x] };

// bind :: (a -> [a]) -> ([a] -> [a])
var bind = function(f) {
  return function(list) {
    var output = [];
    for (var i = 0, n = list.length; i < n; i++) {
      output = output.concat(f(list[i]));
    }
    return output;
  };
};

We can now compose children as desired:

var div = document.getElementsByTagName('div')[0];
var grandchildren = compose(bind(children), bind(children));

grandchildren(unit(div))
// -> [<h1>…</h1>, <p>…</p>, ...]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment