A Simple Explanation of Monad

Introduction

Based on the previous concepts of Functor and Applicative Functor:

  1. Functor transforms values inside the container via .map, with results still in the container.
[2].map(x => x + 3) // [5]
  1. Applicative allows applying “wrapped functions” to “wrapped values”, where computations happen within the wrapping.
Maybe.of(x => x + 3).ap(Maybe.of(2)) // Maybe(5)

But what if the content returned by the wrapping is itself wrapped?

Maybe.of(2).map(x => Maybe.of(x + 3))
// The result is Maybe(Maybe(5))

What is Monad?

function Maybe(value) {
return value == null ? Nothing() : Just(value);
}
function Just(value) {
return {
map: (fn) => Maybe(fn(value)),
flatMap: (fn) => fn(value),
getOrElse: () => value,
};
}
function Nothing() {
return {
map: () => Nothing(),
flatMap: () => Nothing(),
getOrElse: (defaultValue) => defaultValue,
};
}

Using flatMap (also called chain) will automatically flatten the wrapping after computation, allowing the computation to continue smoothly:

getUser(id)
.flatMap(user => getProfile(user))
.flatMap(profile => getPosts(profile))

Example with arrays:

const arr = [1, 2, 1];
const result = arr.flatMap((num) => (num === 2 ? [2, 2] : 1));
console.log(result);
// Expected output: Array [1, 2, 2, 1]

Monad Definition

  • Left Identity

    // Inserting a value into the container and immediately flatMapping a function
    // is equivalent to directly passing that value to the function
    Monad.of(a).flatMap(f) ≡ f(a)
    // Example
    const f = x => Maybe.of(x + 1)
    Maybe.of(2).flatMap(f) // => Maybe(3)
    f(2) // => Maybe(3)
  • Right Identity

    // FlatMapping a Monad with of
    // is equivalent to doing nothing, indicating that flatMap does not break the original structure.
    m.flatMap(Monad.of) ≡ m
    // Example
    Maybe.of(2).flatMap(Maybe.of) // => Maybe(2)
    Maybe.of(2) // => Maybe(2)
  • Associativity

    // Consecutively flatMapping two functions
    // is equivalent to flatMapping a composed function
    // This guarantees that regardless of how you place parentheses in nested links, the result remains consistent.
    m.flatMap(f).flatMap(g)
    m.flatMap(x => f(x).flatMap(g))
    // Example
    const f = x => Maybe.of(x + 1)
    const g = x => Maybe.of(x * 2)
    Maybe.of(2).flatMap(f).flatMap(g) // => Maybe(6)
    Maybe.of(2).flatMap(x => f(x).flatMap(g)) // => Maybe(6)

Conclusion

Monad ⊇ Applicative ⊇ Functor
  • Monad is a type of Applicative and Functor
  • Applicative is a type of Functor

In development, flatMap is more powerful than ap (it can do everything Applicative can do and more), so it is not necessary to implement ap.