柯里化(Currying)

大约 2 分钟

什么是柯里化

柯里化是一种函数的转换方法

它是将一个函数从可调用的 f(a,b,c) 转换为可调用的 f(a)(b)(c)

为什么需要柯里化

可以固定前几个参数,生成实现部分功能的函数

🌰

一个日志函数

function log(date, type, message) {
  const hour = date.getHours();
  const month = date.getMonths();
  return `${hour}:${month} ${type} ${message}`;
}

log(new Date(), "DEBUG", "some bug"); // hh:mm DEBUG some bug

柯里化 - 部分功能函数

const curryLog = curry(log);
// 固定日期的日志函数
const logNow = curryLog(new Date());
logNow("DEBUG", "some bug"); // hh:mm DEBUG some bug
// 固定日期、类型的日志函数
const logNowDebug = curryLog(new Date(), "DEBUG");
logNowDebug("some bug"); // hh:mm DEBUG some bug

简单实现

一个简单的加法函数

function sum(a, b, c) {
  return a + b + c;
}

sum(1,2,3) 函数转为 sum(1)(2)(3) 调用

思考

柯里化函数逻辑:

  1. 接收一个函数
  2. 返回一个函数/调用原函数的结果
    1. 返回一个函数:在最后一个参数之前
    2. 返回调用原函数的结果:在最后一个参数的时候
function curry(func) {
  return function (a) {
    return function (b) {
      return function (c) {
        func(a, b, c);
      };
    };
  };
}

const currySum = curry(sum);
currySum(1)(2)(3); // 6

从以上代码的实现可以看出,原函数接收多少个参数,curry 函数就得返回多少层

function curry(func) {
  return function (a) {
    return function (b) {
      // .... 最终调用原函数
    };
  };
}

存在的问题

  1. 不灵活,无法通用。curry 函数内部无法根据原函数参数个数来适应
  2. 无法以原函数的方式调用柯里化后的函数。既可以以 currySum(1,2,3) 的方式调用,也可以以 currySum(1)(2)(3) 的方式调用

改进

  • 将传入的参数与原函数所需的参数比较
    • 当传入参数个数多于或等于原函数所需参数时,以原函数方式调用
    • 当传入参数个数少于原函数所需参数时,以柯里化方式调用

核心:收集参数,最终将所有参数传入原函数

function curry(func) {
  return function curried(...arg1) {
    // func.length 获取函数所需参数个数
    if (arg1.length >= func.length) {
      // 【传入参数 >= 原函数所需的参数】时则以原函数调用
      return func.apply(this, arg1);
    } else {
      // 【传入参数 < 原函数所需的参数】以柯里化方式调用
      // 返回一个函数,该函数将后续参数拼接,最终以原函数调用
      return function (...arg2) {
        return curried.apply(this, arg1.concat(arg2));
      };
    }
  };
}

const currySum = curry(sum);
currySum(1, 2, 3); // 6
currySum(1, 2)(3); // 6
currySum(1)(2)(3); // 6

总结

柯里化:把接收多参的函数转化成可以逐个调用单个参数并返回接收剩下参数的函数

应用: 使用柯里化可以更容易获取部分功能函数,如🌰中的【日志函数】

注意:只允许转化确定参数个数的函数