JS函数柯理化

207 阅读2分钟

什么是函数的柯理化

柯里化(英语:Currying),又叫函数的部分求值,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

通俗一点来说:柯里化就是一个函数原本有多个参数,只传入一个参数,生成一个新函数,由新函数接收剩下的参数来运行得到结果.

通常柯里化

function curry (fn) {
  return function f () {
    const args = [].slice.call(arguments)
    if(args.length < fn.length) {
      // 参数数量不满足原始函数数量,返回curry函数
      return function () {
        return f.apply(this, args.concat([].slice.call(arguments)))
      }
    } else {
      // 参数数量满足原始函数,触发执行
      return  fn.apply(this, args)
    }
  } 
}

运算结果,如下

const add = curry((a, b, c) => a + b + c)
console.log(add(1,2,3)) // 6
console.log(add(1,3)(2)) // 6
console.log(add(1)(2)(3)) // 6
console.log(add()(1)(2)(3)) // 6

Ramda柯里化

辅助函数

const _ = { '@@functional/placeholder': true }

function _isPlaceholder (a) {
  return a != null &&
         typeof a === 'object' &&
         a['@@functional/placeholder'] === true
}

function _arity (n, fn) {
  switch (n) {
    case 0: return function () { return fn.apply(this, arguments) }
    case 1: return function (a0) { return fn.apply(this, arguments) }
    case 2: return function (a0, a1) { return fn.apply(this, arguments) }
    case 3: return function (a0, a1, a2) { return fn.apply(this, arguments) }
    // ... 省略剩余冗余代码
     case 10: return function(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) { return fn.apply(this, arguments); };
    default: throw new Error('First argument to _arity must be a non-negative integer no greater than ten')
  }
}

核心方法

// length: 柯里化函数参数的个数
// recived: 初始化接收的参数数组,
// fn : 柯里化的函数
function _curryN (length, received, fn) {
  return function () {
    // 存放每次调用函数参数的数组, 在递归调用中就是 recived
    var combined = [];
    var argsIdx = 0;
    var left = length;
    var combinedIdx = 0;
    /* 
      这里同时迭代 recived 和 arguments。
      我们要循环取出每一次 curryN 初始化接收到的参数和调用函数时传入的参数保存在 combined 中,
      这里用一个额外的变量 argsIdx 用于迭代 arguments 的。
    */
    while (combinedIdx < received.length || argsIdx < arguments.length) {
      var result;
      //首先迭代 recived,取出不是占位符的参数放入 combined 中
      if (combinedIdx < received.length && (!_isPlaceholder(received[combinedIdx]) || argsIdx >= arguments.length)) {
        result = received[combinedIdx];
      } else {
        // 如果 recived 已经迭代完了那么将 arguments 放入 combined 中
        result = arguments[argsIdx];
        argsIdx += 1;
      }
      combined[combinedIdx] = result;
      // 如果当前参数不是占位符,则长度减1
      if (!_isPlaceholder(result)) {
        left -= 1;
      }
      combinedIdx += 1;
    }
    // 如果传入参数满足fn参数个数,则直接调用fn,否则递归调用curry函数,反复过滤掉recived的占位符
    return left <= 0 ? fn.apply(this, combined) : _arity(left, _curryN(length, combined, fn));
  };
}

现在我们得到一个带有占位符功能的柯里化函数

function say (name, age, like) { console.log(`我叫${name},我${age}岁了, 我喜欢${like}`) };
const msg = _curryN(3, [], say)
// => 我叫大西瓜,我20岁了, 我喜欢妹子
msg(_, 20)('大西瓜', _,) ('妹子')
// => 我叫吴亦凡,我25岁了, 我喜欢唱rap
msg(_, _, '唱rap')(_, '25')('吴亦凡')
// => 我叫小明,我22岁了, 我喜欢小红
msg('小明')(_, _)(22,  '小红')