JS 柯里化与反柯里化

254 阅读1分钟

原文地址

Currying

柯里化:把多参函数转换为单参函数(是函数式编程的重要概念)。 思路:获取函数形参长度,每一次单参函数的执行目的是获取并保存参数,待最后一次单参函数执行把之前收集的参数传递给原函数执行。

const curry = fn => {
  let length = fn.length
  const args = []
  return function genFn(arg){
    args.push(arg)
    if (--length === 0) return fn(...args)
    return genFn
  }
}

// test case
function sum(a, b, c){
  return a + b + c 
}

curry(sum)(1)(2)(3)
// curry(sum)(1,2)(3) 报错

但是碰到非全柯里化调用则会报错,稍作修改:非单参调用则根据调用实参个数对应修改,另外使用 apply。

const curry = fn => {
  let length = fn.length
  const args = []
  return function genFn(..._args){
    args.push(..._args)
    if ((length -= _args.length) === 0) return fn.apply(this, args)
    return genFn
  }
}

// test case
function sum(a, b, c){
  return a + b + c 
}

console.log(curry(sum)(1)(2)(3))
console.log(curry(sum)(1,2)(3))
console.log(curry(sum)(1)(2,3))
console.log(curry(sum)(1,2,3))

Uncurrying

反柯里化:可以理解为方法的泛化,使特定对象的方法被指定对象借用,有点 call/apply 的感觉是吧,没错就是利用 call/apply 实现。

Function.prototype.uncurry = function(){
  const fn = this
  return (...args) => {
    Function.prototype.call.apply(fn, args)
    // 可理解成 fn.call(...args)
  }
}

结合用法解释下:

Function.prototype.uncurry = function(){
  const fn = this
  return (...args) => {
    Function.prototype.call.apply(fn, args)
    //     执行下面的 push(obj, 'Sean', 'Messi') 
    //  -> Function.prototype.call.apply(Array.prototype.push, [obj, 'Sean', 'Messi'])
    //  -> Array.prototype.push.call(obj, 'Sean', 'Messi')

  }
}

const push = Array.prototype.push.uncurry()
const obj = {}
push(obj, 'Sean', 'Messi') // 对象借用数组的 push 方法

console.log(obj)
// {0: "Sean", 1: "Messi", length: 2}

参考

Javascript 中有趣的反柯里化技术

Uncurrying “this” in JavaScript

JS进阶篇--JS中的反柯里化( uncurrying)