1、什么是柯里化
柯里化(currying)是一种函数转化技术,它把一个接受多个参数的函数转化为可接受单个函数参数,并返回接受余下参数,最终函数返回与原函数相同结果的函数。 说人话就是:柯里化就是实现函数从f(a, b, c)到f(a)(b)(c)的转化。
看一个简单的3数想加的函数add():
function add(a, b, c){
return a + b + c;
}
它对应的柯里化函数curriedAdd():
function curriedAdd(a){
return function(b){
return function(c){
return a + b + c;
}
}
}
//使用
curriedAdd(1)(2)(3); //6
let add10 = curriedAdd(10); //得到一个函数
add10(1)(2); //13;
值得注意的是,柯里化运用了函数闭包的特性。
2、柯里化的意义
f(a)(b)(c)相对f(a, b, c)有什么优势?优点在于参数复用。
考虑一个乘方函数pow(base, exponent),我们可以通过pow(x, y)来计算x的y次方。假设我们需要频繁计算2的幂。则会多次产生如pow(2, 3),pow(2,7), pow(2, 9)之类的调用,尽管第一个参数都是2,但你不得不每次调用都传递。如果使用柯里化的形式,可以有下面的使用方法。
let pow2 = curriedPow(2); //pow2仍然是一个函数
pow2(3); //相当于pow(2,3);
pow2(7);
pow2(9);
可以发现f(a)(b)(c)其实是产生了一系列的函数,我们可以方便得获取到函数的偏函数。
3、实现函数柯里化
如果对一个已经存在的函数,我们需要手动写出它的柯里化函数(如curriedAdd所示), 这代价甚至高于柯里化带来的便利。所以,有没有方法能实现自动对函数的柯里化?在JS这类函数式编程语言中,这是肯定的。看一个对开头add(a,b,c)柯里化的方法。
function currying(func){
return function f(a){
return function f(b){
return function f(c){
return func(a, b, c);
};
};
};
}
function add(a, b, c){
return a + b + c;
}
let curriedAdd = currying(add);
curriedAdd(1)(2)(3); //6
currying实现了对任意的三参数函数的柯里化。为了摆脱函数参数数目的限制,我们考虑一个更通用的版本。
function curry(func){
return function curried(...args){
if(args.length>=func.length){
return func.apply(this, args);
}else{
return function(...args2){
return curried.apply(this, args.concat(args2));
}
}
}
}
现在,你可以使用上面的curry()对任何函数进行柯里化。因为累计参数的原因,你不仅可以通过f(a)(b)(c)的方式调用,也可以用f(a,b)(c), f(a)(b, c), f(a, b, c)的形式调用。
function add(a, b, c, d){
return a + b + c + d;
}
let curriedAdd = curry(add);
curriedAdd(1, 2, 3, 4); //10
curriedAdd(2, 3)(4, 5); //14
//延迟计算
let add7 = curriedAdd(7);
let add14 = add7(7);
let add21 = add14(7);
let result = add21(7); //这里才计算结果。result = 28
你以为完了?其实上面遗漏了一类函数,就是形如func(...args)的不定参数的函数。首先,func.length === 0,导致上面的判断args.length>=func.length无效。其次,对不定参数,我们难以确定其何时计算。比如func(a)(b)(c)我们是该直接计算出它的值吗?还是得到一个偏函数?无论如何,我们需要确定何时计算结果。
一般的,对不定参数的函数,规定在无参数调用时返回结果,即func(a)(), func(a)(b)(), func(a)(b)(c)()这类计算结果。这样一来,就解决了上面的问题,我们把判断条件定为参数是否为空,并且在参数为空时返回结果,否则返回一个偏函数。
//不定参数函数的柯里化
function curry1(func){
let argArr = []
return function temp(...args){
if(!args.length){
let result = func.apply(this,argArr);
argArr = []; //保证下次调用
return result;
}else{
argArr = argArr.concat(args);
return temp;
}
}
}
同样,测试一下使用:
function add1(...args) {
return args.reduce((a, b) => a + b);
}
let cAdd1 = curry1(add1);
console.log(cAdd1(1)(2)(3)(4, 5)()); //15
可以看到,确实如愿工作了。如果想用一个函数实现确定参数与不定参数的函数都能转化,在最外层套一个判断就行了,就不赘述了。
感谢!