偏应用(Partial Application) vs 柯里化
——固定部分参数生成新函数,但不强制一次只传一个
“柯里化是数学家的严谨推演,
偏应用是工程师的实用工具。”
一、核心定义:一句话讲清本质区别
| 概念 | 定义 |
|---|---|
| 柯里化 | 将一个多参数函数转换为一系列单参数函数的链式调用。 |
| 偏应用 | 固定一个函数的部分参数,生成一个接受剩余参数的新函数。 |
关键差异:
柯里化:每次只允许传一个参数
偏应用:可以一次传多个参数
二、直观对比:同一个需求,两种实现
需求:创建一个“乘以 10”的函数
方式 1:柯里化(Currying)
js
const multiply = a => b => a * b;
const times10 = multiply(10); // 预设 a=10
times10(5); // 50multiply(10)必须只传一个参数- 返回函数等待下一个参数
方式 2:偏应用(Partial Application)
js
const multiply = (a, b) => a * b;
const times10 = _.partial(multiply, 10); // 预设 a=10
times10(5); // 50_.partial直接固定第一个参数times10可以接受任意数量的剩余参数
三、执行方式对比
柯里化:严格链式
js
const add = a => b => c => a + b + c;
add(1)(2)(3); // 正确
add(1, 2)(3); // 错误!add(1,2) 返回 NaN 或报错柯里化函数必须一步一步调用。
偏应用:灵活传参
js
const add = (a, b, c) => a + b + c;
const add5 = _.partial(add, 5);
add5(2, 3); // 8
add5(1, 2, 3); // 11(多余的参数会被忽略或报错,取决于实现)偏应用后的函数可以一次传多个剩余参数。
四、底层实现原理
1. 柯里化的自动实现
js
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return (...nextArgs) => curried(...args, ...nextArgs);
}
};
}
// 使用
const sum = (a, b, c) => a + b + c;
const curriedSum = curry(sum);
curriedSum(1)(2)(3); // 6
curriedSum(1, 2)(3); // 6(自动收集)fn.length 提供了参数个数的元信息。
2. 偏应用的实现
js
function partial(fn, ...fixedArgs) {
return function(...remainingArgs) {
return fn.apply(this, fixedArgs.concat(remainingArgs));
};
}
// 使用
const divide = (a, b, c) => a / b / c;
const half = partial(divide, 2); // 固定 a=2
half(4, 1); // 0.5 → divide(2, 4, 1)偏应用直接拼接参数列表,不依赖 fn.length 的递归判断。
五、何时用柯里化?何时用偏应用?
| 场景 | 推荐 | 理由 |
|---|---|---|
| 需要延迟到每个参数都明确 | 柯里化 | 如 React 事件处理器 onClick={handleDelete(id)} |
与 map/filter/reduce 配合 | 柯里化 | 这些函数期望单参数函数,柯里化天然适配 |
| 参数较多且想批量预设 | 偏应用 | 如 const logError = partial(console.log, '[ERROR]') |
使用占位符(如 _) | 偏应用 | Lodash 支持 partial(fn, _, 'fixed'),柯里化难以支持 |
| 函数参数不确定 | 偏应用 | 柯里化依赖 fn.length,对 ...args 函数无效 |
六、高级技巧:结合使用
你可以先柯里化,再进行偏应用:
js
const curriedAdd = a => b => c => a + b + c;
// 偏应用第一个参数
const add5 = curriedAdd(5);
// 再偏应用第二个
const add5And3 = add5(3);
add5And3(2); // 10或者用支持占位符的库(如 Ramda):
js
const add = (a, b, c) => a + b + c;
const R = require('ramda');
const add5Last = R.partial(add, [_, _, 5]); // 最后一个参数固定为 5
add5Last(1, 2); // 8七、工程实践建议
1. 在 FP 库中,优先使用柯里化
- Ramda 的所有函数都是自动柯里化的
- 便于组合:
pipe(map(f), filter(g))
2. 在通用工具中,使用偏应用
- Lodash 的
_.partial更贴近日常开发 - 易于理解,无需函数式背景
3. 自己实现时,考虑灵活性
js
// 支持占位符的偏应用
function flexiblePartial(fn, ...fixedArgs) {
const placeholder = Symbol('placeholder');
return function(...args) {
let argIndex = 0;
const finalArgs = fixedArgs.map(fixed =>
fixed === placeholder ? args[argIndex++] : fixed
);
return fn(...finalArgs, ...args.slice(argIndex));
};
}
const greet = (greeting, name, suffix) => `${greeting}, ${name}${suffix}`;
const sayHi = flexiblePartial(greet, 'Hi', _, '!');
sayHi('Alice'); // 'Hi, Alice!'结语:柯里化是“理想”,偏应用是“现实”
柯里化追求数学上的纯粹性,偏应用拥抱工程中的实用性。
它们的目标一致:通过参数预设,提升函数的复用性和表达力。
选择哪个,取决于你的场景:
- 要代数推导?用柯里化。
- 要快速上手?用偏应用。
最终,它们都是通往声明式编程的路径。