关于柯里化以及反柯里化
一、柯里化(curring)
在维基百科上的解释是: 把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
柯里化的概念简单来说就是把常见的多参数函数改为分步调用, ,逐步传参, 逐步缩小范围, 简单举例
js的写法1
2
3
4function add(num1, num2) {
return num1 + num2;
}
var result = add(1, 2);
这是传统方式的写法, 柯里化之后1
2
3
4
5
6function add(num1) {
return function(num2) {
return num1 + num2;
}
}
var result = add(1)(2);
py的写法
1 | def inc(x): |
黑人问号脸, 有什么卵用???
换个喵神在swift tips中举的例子, 翻译成js
柯里化是一种量产相似方法的好办法, 可以通过柯里化一个方法模板来避免写出很多重复代码, 也方便今后维护
1
2
3
4
5
6
7
8 function greaterThan(comparer) {
return function(num) {
return num > comparer;
}
}
var greaterThan10 = greaterThan(10);
greaterThan10(13) //true
greaterThan10(8) //false
有点工厂模式的味道.
再举一个比较常见, 而且比较重要的例子, bind函数的实现1
2
3
4
5
6
7
8if (!function() {}.bind) {
Function.prototype.bind = function(context) {
var self = this, args = Array.prototype.slice.call(arguments);
return function() {
return self.apply(context, args.slice(1));
}
};
}
另外关于这个函数还可以换方式写, 考察对arguments, apply/call的理解, 也挺有意思的1
2
3
4
5
6
7
8
9if (!function() {}.bind) {
Function.prototype.bind = function() {
var self = this;
var context = [].shift.call(arguments)
return function() {
return self.apply(context, arguments);
}
};
}
关于柯里化的具体作用也存在争议, 有其优点也有缺陷, 因此只有用在正确的地方才是生产力的提升(废话), apple在swift2中添加了原生柯里化的支持, 即函数可以这么写1
2
3
4func add(num1: Int)(num2: Int) -> {
return num1 + num2
}
add(1)(2)
然而在swift3中又去掉了这种方式, 官方的解释是用处有限, 实现复杂
(Curried function declaration syntax func
foo(x: Int)(y: Int)
is of limited usefulness and creates a lot of language and implementation complexity. We should remove it.)
二、反柯里化(uncurring)
反柯里化的作用是,当我们调用某个方法,不用考虑这个对象在被设计时,是否拥有这个方法,只要这个方法适用于它,我们就可以对这个对象使用它。
到这里都要提一下鸭子类型(duck typing)
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。
第一次看到这个概念的时候先想到的是OC里的id类型, id类型的对象可以接受任何方法, 被当做鸭子来测试, 在js中, 提供了call/apply的接口方便我们用类似的思想解决问题.
在js中有一种奇葩类型, 比如arguments, 这是一个类数组的对象, 其本质是个object, 因此没有数组身上的push/pop之类的方法, 然而在某些时候他也需要这些方法, 比如上面用到的1
[].shift.call(arguments);
通过call/apply的方式可以方便的让其他类型的对象调用自身没有的方法, 反柯里化的实现1
2
3
4
5
6
7
8
9
10
11
12
13
14Function.prototype.uncurrying = function() {
var self = this;
return function() {
Function.prototype.call.apply(self, arguments);
}
}
//测试
var obj = {
length: 1,
0: "a"
}
var push = [].push.uncurrying();
push(obj, "b");
console.log(obj); //{ '0': 'a', '1': 'b', length: 2 }
最后的函数调用有种php原生函数的感觉, 把调用方法的对象作为参数传入.
下面这种写法可能更容易理解一些1
2
3
4
5
6
7Function.prototype.uncurring = function() {
var self = this;
var obj = [].shift.call(arguments);
return function() {
self.apply(obj, arguments);
}
}