पाठ 20 / 25

Monads Explained Practically

flatMap and chaining.

Chaining steps that return wrapped values

Problem: you have steps that each return a wrapped value, such as a function returning Option<User> and another taking a User and returning Option<Address>. Using map would give Option<Option<Address>>. A monad adds an operation usually called flatMap, chain, bind or >>= that runs the next step and flattens the result, so a sequence of fallible steps reads as a straight line and stops at the first None or Err. You already use the pattern: Array.prototype.flatMap, Promise.then returning a promise (and async/await), and optional chaining a?.b?.c for nullable values. Monads also need a way to wrap a plain value (of/return/Promise.resolve) and obey laws that make chaining predictable.

flatMap for Option and Result-like chains

Each step can fail; the chain short-circuits.

type Option<T> = { tag: 'some'; value: T } | { tag: 'none' };
const some = <T>(value: T): Option<T> => ({ tag: 'some', value });
const none: Option<never> = { tag: 'none' };
const flatMap = <A, B>(o: Option<A>, f: (a: A) => Option<B>): Option<B> =>
  o.tag === 'some' ? f(o.value) : none;

type User = { id: string; addressId?: string };
type Address = { city: string };
declare function findUser(id: string): Option<User>;
declare function findAddress(id: string): Option<Address>;

const cityOf = (userId: string): Option<string> =>
  flatMap(findUser(userId), u =>
    flatMap(u.addressId ? some(u.addressId) : none, aid =>
      flatMap(findAddress(aid), a => some(a.city))));

// The same shape you already know from promises:
// fetchUser(id).then(u => fetchAddress(u.addressId)).then(a => a.city)

// And from arrays:
[[1, 2], [3]].flatMap(xs => xs);   // [1, 2, 3]

do-notation and async/await

Haskell's do blocks and JavaScript's async/await both exist to hide nested chaining: they let monadic code read like ordinary sequential statements.

त्वरित जाँच: What does flatMap add compared with map?

  • It sorts the values
  • It flattens the result when the function itself returns a wrapped value
  • It runs the function twice
  • It removes the need for a container
Answer

It flattens the result when the function itself returns a wrapped value — map would nest: Option<Option<T>>.