Skip to content

函数柯里化

柯里化(Currying)是一种关于函数的高阶技术。它不仅被用于 JavaScript,还被用于其他编程语言。

概念

函数柯里化(Currying)是一种函数式编程中的技术,它将一个接受多个参数的函数转化为一系列接收单个参数的函数的过程。换句话说,柯里化将一个多参数函数拆分为多个单参数函数的嵌套调用。

数学定义: 对于一个接受两个参数的函数 f(a, b),通过柯里化可以将其改造成 f(a)(b) 的形式。

柯里化的主要目的是提高函数的灵活性和可复用性,使其能够更方便地进行部分参数的预处理(Partial Application)。


函数柯里化的特点

  1. 将一个多参数函数拆解为多个单参数函数。
  2. 每次调用返回一个新函数,等待接收下一个参数。
  3. 参数全部传递完成后,才会执行最终的逻辑。

柯里化的使用场景

  1. 复用参数: 柯里化允许我们提前填充一些参数,从而为后续操作简化函数调用。

  2. 延迟计算: 通过分步调用,柯里化可以实现延迟执行,直到所有参数都传递完成。

  3. 提高代码可读性: 柯里化可以将复杂的逻辑拆分成小型函数,增强代码的可读性和维护性。

  4. 适用于高阶函数: 在函数式编程中,柯里化广泛用于结合高阶函数(如 mapfilter)。


JavaScript 实现柯里化

手动实现一个简单的柯里化函数

js
function curry(func) {
  return function curried(...args) {
    if (args.length >= func.length) {
      return func.apply(this, args);
    } else {
      return function (...nextArgs) {
        return curried.apply(this, args.concat(nextArgs));
      };
    }
  };
}

// 示例函数
function add(a, b, c) {
  return a + b + c;
}

// 使用 curry 函数
const curriedAdd = curry(add);

console.log(curriedAdd(1)(2)(3)); // 输出 6
console.log(curriedAdd(1, 2)(3)); // 输出 6
console.log(curriedAdd(1)(2, 3)); // 输出 6

解释:

1.外层函数 curry(func)

js
function curry(func) {
  // 返回一个内部函数 curried
  return function curried(...args) {
    // ...
  };
}
  • 该函数接受一个多参数函数 func 作为输入。
  • 返回一个名为 curried 的函数,用来处理柯里化的逻辑。
  • ...argsES6 的展开语法(rest 参数),用于表示 接收剩余的参数,将它们合并为一个数组。此处表示 当前阶段传递的所有参数

2. 检查参数长度

js
if (args.length >= func.length) {
}
  • args 是当前已经传递的参数数组(通过 ...args 捕获)。
  • func.length原函数 的形参个数。
  • 如果当前传递的参数个数已经 大于或等于 原函数的形参个数,说明可以调用原函数 func 并直接返回结果。

3. 条件判断如果参数足够

js
return func.apply(this, args);
  • 使用 func.apply(this, args) 调用原函数并返回结果。

如果参数不足

js
return function (...nextArgs) {
  return curried.apply(this, args.concat(nextArgs));
  • 返回一个新函数(箭头函数或普通函数都可以)
  • ...nextArgs...args 一样,表示 接收剩余的参数。当调用 curried 返回的新函数时,这些新的参数会通过 ...nextArgs 捕获
  • 然后用 args.concat(nextArgs) 将之前的参数 args 和新的参数 nextArgs 合并,递归调用 curried

完整运行逻辑

  1. 初始调用 curried 时,收集一部分参数。
  2. 如果参数个数不够,则返回一个新的函数,继续收集参数。
  3. 一旦参数数量满足原函数的要求,就调用原函数并返回结果。

使用箭头函数实现

用 JavaScript 的箭头函数实现柯里化:

js
const curry = (fn) => 
  (...args) => 
    args.length >= fn.length
      ? fn(...args)
      : (...nextArgs) => curry(fn)(...args, ...nextArgs);

// 示例函数
const multiply = (a, b, c) => a * b * c;

// 使用柯里化
const curriedMultiply = curry(multiply);

console.log(curriedMultiply(2)(3)(4)); // 输出 24
console.log(curriedMultiply(2, 3)(4)); // 输出 24
console.log(curriedMultiply(2)(3, 4)); // 输出 24

应用场景示例

  1. 基本使用:
js
function add(a, b, c) { return a + b + c; } const curriedAdd = curry(add); console.log(curriedAdd(1)(2)(3)); // 输出 6 console.log(curriedAdd(1, 2)(3)); // 输出 6 console.log(curriedAdd(1)(2, 3)); // 输出 6
  • 第一次调用 curriedAdd(1),返回一个新函数,等待更多参数。
  • 第二次调用 curriedAdd(1)(2),返回另一个新函数,继续等待参数。
  • 第三次调用 curriedAdd(1)(2)(3),参数够了,调用原函数 add(1, 2, 3)
  1. 创建部分应用函数 柯里化非常适合生成预置参数的部分应用函数。
js
function multiply(a, b, c) {
  return a * b * c;
}

const curriedMultiply = curry(multiply);

const double = curriedMultiply(2); // 固定第一个参数为 2
console.log(double(3, 4)); // 输出 24 (2 * 3 * 4)

const triple = curriedMultiply(3); // 固定第一个参数为 3
console.log(triple(5)(6)); // 输出 90 (3 * 5 * 6)

3.与高阶函数结合:

js
const multiply = (a, b) => a * b;
const curriedMultiply = curry(multiply);

const double = curriedMultiply(2); // 预设第一个参数为 2
console.log([1, 2, 3].map(double)); // 输出 [2, 4, 6]
  1. 延迟执行:
js
const log = curry((level, message) => console[level](message));

// 创建特定日志级别函数
const info = log('info');
const error = log('error');

info('This is an info message'); // 输出到控制台:This is an info message
error('This is an error message'); // 输出到控制台:This is an error message

总结

柯里化在 JavaScript 中是一种非常有用的编程技巧,尤其是在函数式编程场景中。通过柯里化:

  • 函数可以更灵活地传递参数;
  • 代码更简洁、可读性更强;
  • 提供了一种优雅的方式实现参数复用和延迟计算。

实现柯里化的工具函数(如 curry)可以在项目中帮助我们大大提高开发效率!

评论区

欢迎留言、补充或勘误。

xiaoba.blog