what's currying
var add = function(a,b){
return a + b
}
add(2,3) //5
curry化就是把add(2,3)变成add(2)(3)
var add = function(a){
return function(b){
return a + b;
}
}
add(2)(3) //5
我们仔细看看这里发生了什么
var add2 = add(2)
console.log(add2) //[Function]
add2(3) //5
add2(5) //7
我们注意到add(2)返回的是一个函数,这个函数接受一个参数并将其加二后返回。
currying就像一种预加载技术。很多时候我们调用一个函数需要多个参数,但是在某个阶段你只知道其中的一部分参数,不要紧,你先传入已知的参数,并返回新的函数,等所有的参数都被接受的时候,再真正的调用函数。
why currying
currying的好处有很多,很明显的一点是,我们可以用currying来写出更加友好的函数。很多时候,我们可能不知道函数调用的所有参数,这个时候currying就非常有用!
下面我们来写一个可以缩放盒子并给盒子涂颜色的函数
var scale = function(val, stuff){
stuff.size *= val
}
var paint = function(val, stuff){
stuff.color = val
}
var ScaleAndPaint = function(s, color, stuff){
scale(s, stuff)
paint(color, stuff)
}
我们来测试一下这个函数
var box = {
size: 10,
color: 'green'
}
ScaleAndPaint(2,'black',box)
console.log(box) //{ size: 20, color: 'black' }
works great! 但是这时候我们发现,几乎所有的盒子都只需要变成两倍就可以了,可以我们却需要每一次都传入2,这实在太不友好了。下面我们来currying化我们的函数
var ScaleAndPaint = function(s){
return function(color){
return function(stuff){
scale(s, stuff)
paint(color, stuff)
}
}
}
不是都变成两倍大吗,简单
var DoubleAndPaint = ScaleAndPaint(2)
再进一步
var DoubleAndBlue = DoubleAndPaint('blue')
var DoubleAndRed = DoubleAndPaint('Red')
DoubleAndBlue(box) //{ size: 40, color: 'blue' }
lodash
虽然currying化的函数用起来真的很舒服,但是我们注意到,它写起来可比原来的那种麻烦多了,需要多次的嵌套返回函数。另一个问题是在学会currying之前,我们已经写好了我们的函数,现在我们想把它curry化,咋办?
幸运的是,我们有很多框架实现了将普通函数currying化的api,比如lodash和ramda,使用起来不要太简单啊
var curry = require('lodash').curry;
var ScaleAndPaint = curry(function(s, color, stuff){
scale(s, stuff)
paint(color, stuff)
})
ScaleAndPaint(2)('black')(box)
works great!
自己实现!
var slice = Array.prototype.slice;
var toArray = function(a){ return slice.call(a) }
//组合参数列表,因为arguments其实并非真正的数组,所以其没有concat方法
var concatArgs = function(args1, args2){
return args1.concat(toArray(args2));
}
//如果超出原函数的参数列表长度,那么只取前几个
var trimArrLength = function(arr, length){
if ( arr.length > length ) return arr.slice(0, length);
else return arr;
}
//为了简单起见,假设参数总数不会超过6
var createFn = function(fn, args, totalArity){
var remainingArity = totalArity - args.length;
switch(remainingArity){
case 0: return function(){ return processInvocation(fn, concatArgs(args, arguments), totalArity) };
case 1: return function(a){ return processInvocation(fn, concatArgs(args, arguments), totalArity) };
case 2: return function(a,b){ return processInvocation(fn, concatArgs(args, arguments), totalArity) };
case 3: return function(a,b,c){ return processInvocation(fn, concatArgs(args, arguments), totalArity) };
case 4: return function(a,b,c,d){ return processInvocation(fn, concatArgs(args, arguments), totalArity) };
case 5: return function(a,b,c,d,e){ return processInvocation(fn, concatArgs(args, arguments), totalArity) };
case 6: return function(a,b,c,d,e,f){ return processInvocation(fn, concatArgs(args, arguments), totalArity) };
}
}
var processInvocation = function(fn, argsArr, totalArity){
argsArr = trimArrLength(argsArr, totalArity);
if ( argsArr.length === totalArity ) return fn.apply(null, argsArr);
return createFn(fn, argsArr, totalArity);
}
var curry = function(fn){
return createFn(fn, [], fn.length)
}
测试一下
var add = curry(function(a,b,c,d,e){return a+b+c+d+e})
console.log(add(1,2)(3,4)(5)) //15
works great!