Skip to content

偏应用(Partial Application) vs 柯里化

——固定部分参数生成新函数,但不强制一次只传一个

“柯里化是数学家的严谨推演,
偏应用是工程师的实用工具。”

一、核心定义:一句话讲清本质区别

概念定义
柯里化将一个多参数函数转换为一系列单参数函数的链式调用。
偏应用固定一个函数的部分参数,生成一个接受剩余参数的新函数

关键差异:

柯里化:每次只允许传一个参数
偏应用:可以一次传多个参数

二、直观对比:同一个需求,两种实现

需求:创建一个“乘以 10”的函数

方式 1:柯里化(Currying)

js
const multiply = a => b => a * b;
const times10 = multiply(10); // 预设 a=10
times10(5); // 50
  • multiply(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!'

结语:柯里化是“理想”,偏应用是“现实”

柯里化追求数学上的纯粹性,偏应用拥抱工程中的实用性。

它们的目标一致:通过参数预设,提升函数的复用性和表达力

选择哪个,取决于你的场景:

  • 代数推导?用柯里化。
  • 快速上手?用偏应用。

最终,它们都是通往声明式编程的路径。