要跪了,写了半天发现自己研究的原来根本不是柯里化,而是偏函数,被国内很多资料误导了,关于柯里化与偏函数的区别请看这里
http://stackoverflow.com/questions/218025/what-is-the-difference-between-currying-and-partial-application
关于偏函数的目的和作用一直很模糊,网上的资料很多都没有说到重点,大部分只关注于如何实现偏函数,而对其用途闪烁其词,直到最近看了这里:
个人认为所有写代码的技巧目的只有两个:一是提高程序性能,而是使代码模块化,减少耦合增强其可维护性。偏函数的作用很明显是属于第二种。经过偏函数之后,函数的通用性有所降低,但是适用性有所提高。
偏函数并没有我们想象中那么高深,其实我们经常在不知不觉中使用了偏函数,只是你没有发觉而已,让我们举个例子来看偏函数是的用途和如何演变为通用形式的。(关于偏函数的定义请自行百度或者查看我上面给出的链接)
偏函数的演变
假如现在我们基于某个sdk开发一个app,需要调用该sdk提供的api,这些api都有3个参数,分别是调用你的app的id,用户的id和用户的nickname(这个场景在hybrid式的webApp开发很常见)
api1( "myApp", "xiaoming", "小明");
api1("myApp", "xiaohong", "小红");
api1("myApp", "xiaogang", "小刚");
这时候你可能发现了一些端倪,代码中出现了重复的部分(你的app的id),这时候重构代码把公共的部分抽取出来,你可能会这么封装
let myApi1 = function(uid, nickname){
api1("myApp", uid, nickname);
}
myApi1("xiaoming", "小明");
myApi1("xiaohong", "小红");
myApi1("xiaogang", "小刚");
然后你继续开发,发现要调用另外一个api,这个api跟上面那个很相似,只是第2、3个参数变为了用户的令牌和用户id。因为你的app的id是不变的,有了之前的经验,你或许会这么封装。
let myApi2 = function(token, uid){
api2("myApp", token, uid);
}
myApi2("token1", "xiaoming");
myApi2("token2", "xiaohong");
myApi2("token3", "xiaogang");
继续开发的时候,你很有可能会继续调用类似的api3,api4,api5......,这些api的第一个参数都是你的app的id,而在你的某个项目里面,这都是固定不变,那么你的程序里面会出现类似的封装,可以看出又有了重复的代码
let myApi3 = function(p1, p2){
api3("myApp", p1, p2);
}
let myApi4= function(p1, p2){
api4("myApp", p1, p2);
}
let myApi5= function(p1, p2){
api5("myApp", p1, p2);
}
....
此时你或许会这么封装去减少重复
let getMyApi = function(api){return function(p1, p2){
api("myApp", p1, p2)
}
}
let myApi1 = getMyApi(api1)
let myApi2 = getMyApi(api2)
let myApi3 = getMyApi(api3)
一切看起来很好,但是很快又碰到了其它问题,你碰到了另外一种类型的api,它也是3个参数,第1个参数是app的id,第2个参数是你的app的名字,两个参数都是固定不变的,那么根据上面封装经验,你可能会这么封装:
let getMyApi2 = function(api){return function(p2){
api("myApp", "myAppName", p2)
}
}
let myApi4 = getMyApi2(api4)
let myApi5 = getMyApi2(api5)
let myApi6 = getMyApi2(api6)
这时你会发现 getMyApi 和 getMyApi2 似乎有一些相似的地方,它们都是:
1,将需要调用的api(一个函数)作为第1个参数传入
2,然后固定住部分不变的参数
3,然后返回新的函数
既然有相似的地方,说明还可以做进一步的抽象,我们可以这么写
let generalGetMyApi = function(api) {//把函数和需要固定住的参数都传递进来
let settledArgs = [].splice.call(arguments, 1);//返回一个新的函数,这个函数包含了已经固定住部分参数,并且传入一些易变的参数
return function() {//将不变的参数和易变的参数重新传进这个函数再执行
let mutableArguments =arguments;
api.call(null, [].concat.apply(settledArgs, mutableArguments));
}
}
以上就是一个简单的通用的偏函数的做法,有了这种做法,上面我们重构的问题都非常好解决了
当遇到第一种类型的api(第1参数需要固定),那么可以这样做
let myApi1 = generalGetMyApi(api1, "myApp1");
myApi1("xiaoming", "小明");
myApi1("xiaohong", "小红");
myApi1("xiaogang", "小刚");
let myApi12 = generalGetMyApi(api2, "myApp1");myApi2("token1", "xiaoming");myApi2("token2", "xiaohong");myApi2("token3", "xiaogang");
当遇到第二种类型的api(第1、2参数需要固定),那么可以这样做
let myApi3 = generalGetMyApi(api3, "myApp1","nameOfApp1");
myApi2("other args1")
myApi2("other args2")
myApi2("other args3")
现在代码没有了重复的味道,并且函数generalGetMyApi还可以在其它项目中复用。这就是偏函数的目的,将函数的通用性降低,提高了函数的适用性,而这一些是通过缓存部分参数实现的。