=====================================
函数的柯里化与反柯里化
=====================================
[这是一篇比较久以前的总结了,如有错漏,请指正!]javascript
维基百科的名词解释:柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,而且返回接受余下的参数并且返回结果的新函数的技术。这个技术由 Christopher Strachey 以逻辑学家哈斯凯尔·加里命名的,尽管它是 Moses Schönfinkel 和 Gottlob Frege 发明的。html
函数柯里化,是固定部分参数,返回一个接受剩余参数的函数,也称为部分计算函数,目的是为了缩小适用范围,建立一个针对性更强的函数。
提升了代码的合理性,更重的它突出一种思想---下降适用范围,提升适用性。 对于一个已有函数,对其约定好其中的某些参数输入,而后生成一个更有好的、更符合业务逻辑的函数。
柯里化就是一个函数在参数没给全时返回另外一个函数,返回的函数的参数正好是余下的参数。 好比:你制定了x和y, 如2的3次方,就返回8, 若是你只制定x为2,y没指定, 那么就返回一个函数:2的y次方, 这个函数只有一个参数:y。这样就很是容易理解吧。
function currying(fn) {
var __args = Array.prototype.slice.call(arguments, 1);
return function () {
var __inargs = slice.call(arguments);
return fn.apply(null, __args.concat(__inargs));
};
}前端
function square(i) { return i * i; } function dubble(i) { return i *= 2; } function map(handeler, list) { return list.map(handeler); } // 数组的每一项平方 map(square, [1, 2, 3, 4, 5]); map(square, [6, 7, 8, 9, 10]); map(square, [10, 20, 30, 40, 50]); // 数组的每一项加倍 map(dubble, [1, 2, 3, 4, 5]); map(dubble, [6, 7, 8, 9, 10]); map(dubble, [10, 20, 30, 40, 50]);
例子中,建立了一个map通用函数,用于适应不一样的应用场景。显然,通用性不用怀疑。 同时,例子中重复传入了相同的处理函数:square和dubble。 应用中这种可能会更多。固然,【通用性的加强必然带来适用性的减弱】
使用currying function square(i) { return i * i; } function dubble(i) { return i *= 2; } function map(handeler, list) { return list.map(handeler); } var mapSQ = currying(map, square); mapSQ([1, 2, 3, 4, 5]); mapSQ([6, 7, 8, 9, 10]); mapSQ([10, 20, 30, 40, 50]); // ...... var mapDB = currying(map, dubble); mapDB([1, 2, 3, 4, 5]); mapDB([6, 7, 8, 9, 10]); mapDB([10, 20, 30, 40, 50]); // ...... 【缩小了函数的适用范围,但同时提升了函数的适用性】
var curry = function(fn){ var _args = []; return function(){ if(arguments.length === 0){ return fn.apply(this,_args); } [].push.apply(_args,arguments); // console.log(arguments.callee); return arguments.callee; } } var sum=0; var add =function(){ for (var i = 0,c; c=arguments[i++];){ sum+=c; } return sum; }; var add = curry(add); var res1 = add(1)(2)(3)(); console.log(res1) // 6 // add(1); // add(2); // add(3); // var res2=add(); // console.log(res2)
currying函数, 即可以延迟到最后一刻才一块儿计算, 好处不言而喻, 在不少场合能够避免无谓的计算, 节省性能, 也是实现惰性求值的一种方案.
提早把易变因素,传参固定下来,生成一个更明确的应用函数。最典型的表明应用,是bind函数用以固定this这个易变对象。 Function.prototype.bind = function(context) { var _this = this, _args = Array.prototype.slice.call(arguments, 1); return function() { return _this.apply(context, _args.concat( Array.prototype.slice.call(arguments))); } } bind函数自己就是柯里化的一种体现,函数能够经过bind绑定新的上下文环境来改变其所处的上下文,并生成一个新的函数。这里第一次传入的参数_args就是函数自己的信息,被保存在了函数的闭包之中,就是柯里化的属性;以后的第二个参数arguments,就是在函数调用的过程当中再传递的剩余参数。
反科里化的话题来自javascript之父Brendan Eich去年的一段twitter.
http://www.jb51.net/article/32435.htm - javascript中有趣的反柯里化深刻分析
http://sombie.diandian.com/post/2013-06-28/40050585369 -【WEB前端】由JavaScript反柯里化所想到的java
让你自定义的对象拥有原生JS对象的方法,并利用鸭子类型的特征扩展其使用范围。数组
扩展函数适用范围的方法 把函数也看成普通数据来使用, 当函数名自己是个变量的时候, 这种调用方法特别方便. 扩大函数的适用性,使原本做为特定对象所拥有功能的函数能够对全体对象使用
Function.prototype.uncurry = function() { var _this = this return function() { return Function.prototype.call.apply(_this, arguments) } }
短小精悍,科学上讲,浓缩的都是精品,但越精品的每每越难以理解。分解一下: 1 为Function原型添加unCurrying方法,这样全部的function均可以被借用; 2 返回一个借用其它方法的函数,这是目的; 3 借用call方法实现,但call方法参数传入呢?借用apply,至此完毕。
最终是把this.method转化成 method(this,arg1,arg2....)以实现方法借用和this的泛化。闭包
foo = somefun.uncurry();
foo(obj, args...) <==> obj.somefun(args)。app
例:让Object也拥有push的方法
var push = Array.prototype.push.uncurry()
push({},'aa');函数
//将原生的bind函数转换为全局的bind var bind = Function.prototype.call.bind(Function.prototype.bind); module={ debug:function(){ console.log.apply(console,arguments); } }; var debug = bind(module.debug,module); debug('adsfadsf','444',[1,2,3]); //输出:adsfadsf 444 [1, 2, 3] //将原生的push函数转换为全局的push var push = Function.prototype.call.bind([].push); var person ={ name:'aa', age:33 }; push(person,'44'); debug(person); //输出:Object {0: "44", name: "aa", age: 33, length: 1} 为何push函数能够应用到非数组对象上? v8引擎里面Array.prototype.push的代码 function ArrayPush() { var n = TO_UINT32( this.length ); var m = %_ArgumentsLength(); for (var i = 0; i < m; i++) { this[i+n] = %_Arguments(i); //属性拷贝 this.length = n + m; //修正length return this.length; } } ArrayPush方法没有对this的类型作任何显示的限制,因此理论上任何对象均可以被传入ArrayPush这个访问者。 push能够应该到相似数组类型的对象上,即鸭子类型
故事:
好久之前有个皇帝喜欢听鸭子呱呱叫,因而他召集大臣组建一个一千只鸭子的合唱团。大臣把全国的鸭子都抓来了,最后始终还差一只。有天终于来了一只挺身而出的鸡,这只鸡说它也会呱呱叫,好吧在这个故事的设定里,它确实会呱呱叫。 后来故事的发展很明显,这只鸡混到了鸭子的合唱团中。— 皇帝只是想听呱呱叫,他才不在意你是鸭子仍是鸡呢。
这个就是鸭子类型的概念,在javascript里面,不少函数都不作对象的类型检测,而是只关心这些对象能作什么。
Array构造器和String构造器的prototype上的方法就被特地设计成了鸭子类型。这些方法不对this的数据类型作任何校验。这也就是为何arguments能冒充array调用push方法.post