Monad:为什么 Promise 是 Monad?
——flatMap(then)满足左单位律、右单位律、结合律
“Monad 就像洋葱:
你以为剥开一层是核心,
结果发现是另一层 then。”
一、问题:嵌套的函子(Functor)灾难
我们已知 Promise 是函子(Functor),支持 map:
Promise.resolve(5)
.then(x => x * 2) // Promise{10}但问题来了:如果 map 的函数也返回一个 Promise?
const fetchUser = id => fetch(`/api/users/${id}`).then(r => r.json());
const fetchPosts = user => fetch(`/api/posts?uid=${user.id}`).then(r => r.json());
// 尝试链式调用
Promise.resolve(1)
.then(fetchUser) // → Promise{ Promise{user} }
.then(fetchPosts); // ❌ 失败!收到的是 Promise,不是 user结果是 Promise<Promise<Posts>> —— 嵌套了!
我们需要一种方式“压平”嵌套,这就是 Monad 的使命。
二、Monad 的核心:chain(aka flatMap, bind, then)
函子(Functor):
F.map(f) // f: A → B → F<B>单子(Monad):
M.chain(f) // f: A → M<B> → M<B>关键区别:
map:函数返回普通值chain:函数返回另一个容器(M<B>)chain会自动“解包”并合并容器,避免嵌套
Promise.then 就是 chain
Promise.resolve(1)
.then(fetchUser) // fetchUser 返回 Promise<User>
.then(fetchPosts) // ✅ 自动解包,收到的是 User
.then(posts => console.log(posts));then 不仅映射,还扁平化结果,所以 Promise 是 Monad。
三、Monad 的三大定律
要称为 Monad,必须满足三个数学定律。
1. 左单位律(Left Identity)
M.of(a).chain(f) ≡ f(a)
// 左边
Promise.resolve(5).then(x => Promise.resolve(x * 2))
// → Promise{10}
// 右边
(x => Promise.resolve(x * 2))(5)
// → Promise{10}
// 相等!“先提升再链” ≡ “直接调用函数”
2. 右单位律(Right Identity)
m.chain(M.of) ≡ m
const m = Promise.resolve(42);
// 左边
m.then(Promise.resolve) // Promise{42}
// 右边
m // Promise{42}
// 相等!“链上提升函数”不改变原值
3. 结合律(Associativity)
m.chain(f).chain(g) ≡ m.chain(x => f(x).chain(g))
const m = Promise.resolve(5);
const f = x => Promise.resolve(x + 1);
const g = x => Promise.resolve(x * 2);
// 左边:先 f 后 g
m.then(f).then(g)
// 5 → 6 → 12
// 右边:f 和 g 组合后再 chain
m.then(x => f(x).then(g))
// 5 → (6 → 12) → 12
// 结果相同这保证了链式调用的可重组性。
四、为什么 Monad 如此强大?
1. 处理异步依赖(串行)
getUser(1)
.then(profile)
.then(save)
.then(notify);每个步骤依赖前一个结果,Monad 让你像写同步代码一样组合异步操作。
2. 统一错误处理
fetch('/data')
.then(parse)
.then(process)
.catch(handleError)
.then(finalize);失败时自动跳过后续 then,集中处理。
3. 可组合的副作用
const dbOp = () =>
connect()
.then(insert)
.then(commit)
.catch(rollback);将复杂的副作用封装成可复用的 Monad 链。
五、其他常见的 Monad
1. Array 是 Monad
[1,2].flatMap(x => [x, x*2]) // [1,2,2,4]flatMap 是 chain,Array.of 是 of。
2. Maybe 是 Monad
Maybe.of(5)
.chain(x => x > 0 ? Maybe.of(x*2) : Maybe.of(null))避免空值,优雅失败。
3. Either 是 Monad
Either.of(5)
.chain(x => x > 0 ? Right(x*2) : Left('invalid'))用于业务逻辑错误处理。
六、Monad 在工程中的意义
更好的异步流控制
- 替代回调地狱
- 比
async/await更底层(async/await是 Monad 语法糖)
可预测的错误传播
- 错误自动沿链传播
- 集中处理,避免重复
try/catch
函数式架构基石
- Redux-Saga 使用 Generator + Monad 思想
- RxJS Observable 是 Monad
七、async/await 是 Monad 的语法糖
async function process() {
const user = await getUser(1);
const posts = await getPosts(user.id);
return { user, posts };
}等价于:
getUser(1)
.then(user => getPosts(user.id))
.then(posts => ({ user, posts }));await 解包 Promise,async 函数自动返回 Promise —— 完美符合 Monad 行为。
结语:Monad 是“可组合的计算上下文”
它让你在容器中进行复杂的、依赖性的操作,而无需手动解包。
Promise 之所以强大,
不是因为它异步,
而是因为它是一个符合数学定律的 Monad。
当你调用 then,
你不是在“注册回调”,
你是在构造一个从 A 到 B 的可组合、可推理的计算管道。
这,就是函数式编程的深水区。