Functional JS: Monads

ยท 610 words ยท 3 minute read

Programming with monads became very popular with the Haskell language, but nowadays they can be used in other languages like JavaScript.

A description and demonstration of monads in the context of functional programming was originally written by Philip Wadler in 1995 in Monads for functional programming. In that article, he provided concrete examples for their application.

In a general sense, monads can be defined as structures that store values of any type and allow operations to be performed on them. Additionally, these structures are not exhausted and facilitate composability. They are a special case of functors, with additional capabilities such as chaining operations. You can explore the concept of functors in another post: Functional JS: Functors.

In JavaScript, a monad is an object that encapsulates a value and has methods that allow operations to be performed on it, returning another monad. For example, the flatMap method. Additionally, it is necessary for a monad to provide a way to convert a value to a monad, through some mechanism. The most common example is to use the of method to convert a value to an object.

Let’s see a simple example of what could be a monad object in JS, which has no purpose other than executing the function passed to flatMap as long as its value is different from null or undefined.

class CustomMonad {
  constructor(value) {
    this.value = value;
  }

  flatMap(fn) {
    if (this.value === null || this.value === undefined) {
      return this;
    }

    return fn(this.value);
  }

  static of(value) {
    return new CustomMonad(value);
  }
}

It’s a fairly simple mechanism that allows for consistent chaining of operations.

Laws of monads ๐Ÿ”—

In the same article we discussed before, the three fundamental laws that any implementation of a monad must comply with in a functional language are described. The goal is to ensure coherence and composability.

Left identity law ๐Ÿ”—

return a >>= k is equivalent to k a

const identityMonad = (value) => [value];

const a = 5;
const k = (x) => [x * 2];

// return a >>= k
const leftIdentity = identityMonad(a).flatMap(k);

// k a
const rightSide = k(a);

console.log(leftIdentity); // [10]
console.log(rightSide); // [10]

Right identity law ๐Ÿ”—

m >>= return is equivalent to m

const identityMonad = (value) => [value];

const m = [3, 6, 9];

// m >>= return
const rightIdentity = m.flatMap(identityMonad);

// m
const leftSide = m;

console.log(rightIdentity); // [3, 6, 9]
console.log(leftSide); // [3, 6, 9]

Associativity law ๐Ÿ”—

(m >>= f) >>= g is equivalent to m >>= (\x -> f x >>= g)

const identityMonad = (value) => [value];

const m = [2, 4];
const f = (x) => [x + 1];
const g = (x) => [x * 2];

// (m >>= f) >>= g
const associativityLeft = m.flatMap(f).flatMap(g);

// m >>= (x => f(x) >>= g)
const associativityRight = m.flatMap((x) => f(x).flatMap(g));

console.log(associativityLeft); // [5, 7]
console.log(associativityRight); // [5, 7]

Illustrious examples ๐Ÿ”—

In the interesting article jQuery is a monad the concept of monad is addressed and it is argued how jQuery meets the requirements to be called a monad. The popular tool, whose main composition strategy was obtained through its method chaining, was actually a monad. This form of composition allowed chaining operations without the need to store the intermediate values of each operation.

Additionally, it explains in a formidable way and in very accessible language each of the 3 laws of monads:

  • A monad is a wrapper around another type
  • All monads must have a function to wrap themselves around other data types
  • All monads must be able to feed the value or values that they wrap into another function, as long as that function eventually returns a monad