Skip to content

🔥函数管道(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 → hh ← 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 |> toString

pipe 是为未来语言特性做的准备。

结语:工具服务于思维

  • pipe 写代码,像写故事:一步一步推进。
  • compose 写代码,像写公式:层层包裹抽象。

两者都强大,
pipe 更贴近人类认知

在你的下一个项目中,
不妨从 pipe 开始,
让函数式变得可读、可教、可用