🔥函数管道(pipe) vs 组合(compose)
——pipe(f, g, h)(x) 更符合阅读顺序,compose(h, g, f)(x) 符合数学习惯
“从左到右是人类的思维,
从右到左是数学的优雅。”
一、核心区别:数据流动方向
| 函数 | 数据流向 | 示例 |
|---|---|---|
pipe | 从左到右 | x → f → g → h |
compose | 从右到左 | h ← g ← f ← x |
直观对比
js
// pipe: 左 → 右
pipe(f, g, h)(x)
// 等价于:h(g(f(x)))
// compose: 右 ← 左
compose(h, g, f)(x)
// 等价于:h(g(f(x)))结果相同,顺序相反。
二、pipe:符合直觉的“流水线”
为什么更易读?
人类阅读是从左到右的线性过程。
js
const getUserEmail = pipe(
getUserById, // 1. 获取用户
getProfile, // 2. 提取资料
getEmail // 3. 获取邮箱
);
getUserEmail(123);像读句子: “先找用户,再取资料,最后拿邮箱。”
实现 pipe
js
const pipe = (...fns) => (value) =>
fns.reduce((acc, fn) => fn(acc), value);真实场景:数据处理流水线
js
const processOrder = pipe(
validateOrder, // 验证订单
applyDiscount, // 应用折扣
calculateTax, // 计算税费
saveToDB // 保存
);清晰、可维护、易调试
三、compose:数学的“函数复合”
来源:数学记号 (f ∘ g)(x) = f(g(x))
text
// 数学:先 g,再 f
(f ∘ g)(x) ≡ f(g(x))
// JS compose
compose(f, g)(x) === f(g(x))注意:compose(f, g) 是 f after g,但参数顺序是 f, g,执行是 g 先。
为什么用 compose?
- 函数式传统(Haskell、Ramda 默认)
- 与数学公式一致
- 在类型推导中更自然
js
// Ramda 风格
import { compose, prop, toUpper } from 'ramda';
const getUserNameUpper = compose(
toUpper,
prop('name'),
getUserById
);虽然从右到左,但函数式开发者已习惯。
实现 compose
js
const compose = (...fns) => (value) =>
fns.reduceRight((acc, fn) => fn(acc), value);四、对比表格
| 特性 | pipe(f, g, h) | compose(h, g, f) |
|---|---|---|
| 数据流 | x → f → g → h | h ← g ← f ← x |
| 阅读顺序 | ✅ 从左到右(自然) | ❌ 从右到左(反直觉) |
| 参数顺序 | 执行顺序 | 逆执行顺序 |
| 调试友好 | ✅ 中间值易插入 tap | ❌ 需反向思考 |
| 函数式传统 | 较新(如 RxJS) | 经典(如 Haskell) |
| 推荐场景 | 业务逻辑、新手团队 | FP 库、学术项目 |
五、真实选择:团队与上下文
推荐 pipe 的场景
- 大多数业务项目
- 新团队引入函数式
- 数据转换流水线
- React + Redux 中间件风格
js
// 像 middleware 一样自然
const handler = pipe(
logInput,
sanitize,
process,
respond
);推荐 compose 的场景
- 使用 Ramda、fp-ts 等库
- 与数学模型强相关
- 团队熟悉 FP 传统
js
// fp-ts 风格
import * as E from 'fp-ts/Either';
import { pipe } from 'fp-ts/function';
const result = pipe(
E.of(4),
E.map(x => x * 2),
E.chain(x => x > 5 ? E.right(x) : E.left('too small'))
);注意:fp-ts 虽然叫 pipe,但它是 pipe!说明趋势。
六、如何选择?一个简单规则
如果你要向右箭头解释流程,用 pipe。
如果你要引用 (f ∘ g)(x) 公式,用 compose。
在现代 JS 开发中,pipe 正在成为主流,因为它:
- 更易教
- 更少出错
- 与
|>(Pipeline Operator)提案一致
七、Bonus:与 Pipeline Operator 结合
提案:|>(TC39 Pipeline Operator)
js
let result = 5
|> double
|> add10
|> toString;
// 等价于:toString(add10(double(5)))这正是 pipe 的语法糖!
js
// 当前
pipe(double, add10, toString)(5)
// 未来(提案)
5 |> double |> add10 |> toStringpipe 是为未来语言特性做的准备。
结语:工具服务于思维
- 用
pipe写代码,像写故事:一步一步推进。 - 用
compose写代码,像写公式:层层包裹抽象。
两者都强大,
但 pipe 更贴近人类认知。
在你的下一个项目中,
不妨从 pipe 开始,
让函数式变得可读、可教、可用。