先說 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。名字很嚇人,概念很日常。