函数柯里化

前言

今天考了拼多多的笔试题,简答里面有一道提到函数柯里化的问题,总结一下。

什么是函数柯里化

  维基百科的解释为:在计算机科学中,柯里化(Currying),又称卡瑞化或者加里化,是把接受多个参数的函数变成接受一个单一参数的函数,并且返回接受余下的参数而且返回结果的新函数的技术。简单地说,就是将一个接受多个参数的函数拆分成一个或者多个参数但参数总和不变的多个函数。举个例子:

// 柯里化之前
function add(a, b, c){
  return a + b + c;
}
add(1, 2, 3);  // 6

// 手动柯里化
function add(a){
  return (b) => (c) => a + b + c;
}
add(1)(2)(3);  // 6

  上述例子就是将add函数中的三个参数分别拆开成单个然后执行。但是此函数只是三个参数,如果参数很多并且拆开的参数个数不一那么函数写起来就会十分麻烦…所以为了解决上述问题,我们需要写一个名为curring的函数来替我们完成柯里化!

如何实现函数柯里化

  不妨来分析下柯里化的最本质需求是什么。看完上个例子不难想到就是分割参数并且不仅仅是单个分割,任意长度的分割都可以!看到参数就会联想到arguments,没错,就是利用arguments来进行操作。

function currying(fn){
  // 将我们要柯里化的函数分割出来
  var args = Array.prototype.slice.call(arguments,1);
  return function(){
    // 将currying剩下的参数和返回的函数的参数整合
    var newArgs = args.concat([...arguments]);
    return fn.apply(null, newArgs);
  }
}

var add = (a, b, c) => a + b + c;
currying(add, 1)(2, 3);  // 6
currying(add, 1, 2)(3);  // 6
currying(add)(1, 2, 3);  // 6

  有没有发现,我们可以将参数拆成两部分了,但是只能是两部分,这个并不是我们想要的最终结果:currying(add)(1)(2)(3)。为什么呢?因为我们只把参数分成了两部分,两次执行后函数就出结果了,如果想要将参数分成更多就需要判断已经分割的参数的长度和剩余参数的长度,所以需要对上述函数改进。

function currying(fn, length){
  // fn函数的参数个数
  length = length || fn.length;
  return function(){
    // 没有完全柯里化,还有剩余参数
    if(arguments.length < length){
      var args = [fn].concat([...arguments]);
      return length - arguments.length > 0 ? 
        currying(subCurry.apply(null, args), length - arguments.length) : subCurry.call(args);
    }else{
      return fn.apply(null, arguments);
    }
  }
}

function subCurry(fn){
  var args = Array.prototype.slice.call(arguments,1);
  return function(){
    var newArgs = args.concat([...arguments]);
    return fn.apply(null, newArgs);
  }
}

currying(add)(1)(2)(3);  // 6
currying(add)(1)(2, 3);  // 6