先說 Functor

Functor 是一個「有 map 的 container」。

// 陣列是 Functor:有 map,可以對 container 裡的值做轉換
[1, 2, 3].map(x => x * 2)  // [2, 4, 6]
// 轉換的是 container 裡的值,container 本身的結構(Array)不變
 
// Promise 是 Functor:有 then,可以對 container 裡的值做轉換
Promise.resolve(5).then(x => x * 2)  // Promise<10>
// 值被轉換,container 本身(Promise)不變

Functor 的法則:

  • map(id) = id(map identity function 不改變容器)
  • map(f).map(g) = map(x => g(f(x)))(可以組合)

Monad 是可以 flatMap 的 Functor

問題來了:如果你 map 的函式本身也返回一個 container 怎麼辦?

const userIds = [1, 2, 3];
const getOrders = (userId) => [userId * 10, userId * 20];  // 返回陣列
 
userIds.map(getOrders);
// [[10, 20], [20, 40], [30, 60]]  ← 嵌套陣列,不是我們要的
 
userIds.flatMap(getOrders);
// [10, 20, 20, 40, 30, 60]  ← flatMap = map + 展開一層

Monad = Functor + flatMap(chain / then / bind)

flatMap 讓你鏈式組合會返回 container 的函式,不會產生嵌套。


Promise 就是 Monad

// 每個步驟返回一個 Promise(container)
// then(flatMap)讓它們能鏈式組合,而不是 Promise<Promise<User>>
 
fetchUser(1)           // Promise<User>
    .then(user =>
        fetchOrders(user.id)  // 返回 Promise<Order[]>,不是 Order[]
    )                  // Promise<Order[]>,而不是 Promise<Promise<Order[]>>
    .then(orders =>
        orders.filter(o => o.status === 'pending')
    )
    .catch(err => handleError(err));

如果 .then 只是 map(Functor),得到的會是 Promise<Promise<Order[]>>——一個巢狀的 Promise,需要解兩層。flatMap 的語意就是「幫你解一層嵌套」。


Optional / Maybe:處理可能不存在的值

// 不用 Optional:到處是 null check
function getCity(user: User | null): string | null {
    if (user === null) return null;
    if (user.address === null) return null;
    return user.address.city;
}
 
// 用 Optional 的 Monad 鏈(TypeScript 沒有內建,用 fp-ts 或自實作)
const getCity = (user: User | null): string | null =>
    Optional.of(user)
        .flatMap(u => Optional.of(u.address))
        .flatMap(a => Optional.of(a.city))
        .getOrElse(null);

Java 的 Optional.flatMap、Scala 的 Option、Kotlin 的 ?.(safe call operator)——都是 Maybe Monad 的實作。


心智模型:Monad 是「可組合的 context」

你不需要記定義,記這個心智模型就夠了:

Monad 讓你把「有 context 的值」(Promise、Optional、Array、Either)串起來,而不會陷入嵌套地獄。

  • Promise 的 context 是「非同步」
  • Optional 的 context 是「可能不存在」
  • Array 的 context 是「多個值」
  • Either 的 context 是「可能失敗(Left)或成功(Right)」

每次你用 .then().flatMap()?.——你就在用 Monad。名字很嚇人,概念很日常。