柯里化函数实现

手写 JavaScript 柯里化 (Curry) 函数

问题

实现一个 curry 函数,将多参数函数转换为可以逐个传参调用的函数。

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

const curriedAdd = curry(add);
curriedAdd(1)(2)(3); // 6
curriedAdd(1, 2)(3); // 6
curriedAdd(1)(2, 3); // 6

解答

基础实现

function curry(fn) {
  return function curried(...args) {
    // 如果传入的参数数量 >= 原函数参数数量,直接执行
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    // 否则返回一个新函数,继续收集参数
    return function (...nextArgs) {
      return curried.apply(this, args.concat(nextArgs));
    };
  };
}

使用示例

// 原函数
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
console.log(curriedAdd(1, 2, 3));    // 6

支持占位符的实现

function curry(fn, placeholder = curry.placeholder) {
  return function curried(...args) {
    // 检查是否有占位符,以及参数是否足够
    const complete = args.length >= fn.length 
      && !args.slice(0, fn.length).includes(placeholder);
    
    if (complete) {
      return fn.apply(this, args);
    }
    
    return function (...nextArgs) {
      // 用 nextArgs 填充 args 中的占位符
      const merged = args.map(arg => 
        arg === placeholder && nextArgs.length ? nextArgs.shift() : arg
      );
      return curried.apply(this, merged.concat(nextArgs));
    };
  };
}

// 定义占位符
curry.placeholder = Symbol('placeholder');

占位符使用示例

const _ = curry.placeholder;

function log(a, b, c) {
  console.log(a, b, c);
}

const curriedLog = curry(log);

curriedLog(_, 2, 3)(1);  // 1 2 3
curriedLog(_, _, 3)(1)(2); // 1 2 3
curriedLog(1, _, 3)(2);  // 1 2 3

ES6 简洁写法

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

关键点

  • fn.length 获取函数的形参个数
  • 参数不足时返回新函数继续收集参数
  • 参数足够时调用原函数并返回结果
  • 使用 apply 或展开运算符传递参数数组
  • 占位符实现需要在合并参数时替换占位位置