前面的话

  函数柯里化currying的概念最早由俄国数学家Moses Schönfinkel发明,而后由著名的数理逻辑学家Haskell Curry将其丰富和发展,currying由此得名。本文将详细介绍函数柯里化(curring)

定义

  currying又称部分求值。一个currying的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值

  从字面上理解currying并不太容易,下面通过编写一个计算每月开销的函数来解释函数柯里化currying

每月开销函数

  在每天结束之前,都要记录今天花掉了多少钱。代码如下:

var monthlyCost = ;
var cost = function( money ){
monthlyCost += money;
};
cost( ); // 第 1 天开销
cost( ); // 第 2 天开销
cost( ); // 第 3 天开销
//...
cost( ); // 第 30 天开销
alert ( monthlyCost ); // 输出1个月的总开销

  每天结束后都会记录并计算到今天为止花掉的钱。但其实并不太关心每天花掉了多少钱,而只想知道到月底的时候会花掉多少钱。也就是说,实际上只需要在月底计算一次

  如果在每个月的前29天,都只是保存好当天的开销,直到最后一天才进行求值计算,这样就达到了我们的要求,代码如下

  var cost = (function () {
var args = [];
return function () {
//如果没有参数,则计算args数组中的和
if (arguments.length === ) {
var money = ;
for (var i = , l = args.length; i < l; i++) {
money += args[i];
}
return money;
//如果有参数,则只能是将数据传到args数组中
} else {
[].push.apply(args, arguments);
}
}
})();
cost(); // 未真正求值
cost(); // 未真正求值
cost(); // 未真正求值
console.log(cost()); // 求值并输出:600

通用函数

  下面来编写一个通用的柯里化函数currying,currying接受一个参数,即将要被currying的函数。如果和上面的例子结合,则这个函数的作用是遍历本月每天的开销并求出它们的总和

  var currying = function (fn) {
var args = [];
return function () {
if (arguments.length === ) {
return fn.apply(this, args);
} else {
[].push.apply(args, arguments);
return arguments.callee;
}
}
};
var cost = (function () {
var money = ;
return function () {
for (var i = , l = arguments.length; i < l; i++) {
money += arguments[i];
}
return money;
}
})();
var cost = currying(cost); // 转化成 currying 函数
cost(); // 未真正求值
cost(); // 未真正求值
cost(); // 未真正求值
alert(cost()); // 求值并输出:600

  至此,完成了一个currying函数的编写。当调用cost()时,如果明确地带上了一些参数,表示此时并不进行真正的求值计算,而是把这些参数保存起来,此时让cost函数返回另外一个函数。只有以不带参数的形式执行cost()时,才利用前面保存的所有参数,真正开始进行求值计算

可传参函数

  实际上,柯里化函数不仅可以接收要柯里化的函数作为参数,也可以接收一些必要参数,下面是函数柯里化(currying)的改进代码

  var currying = function (fn) {
var args = [];
//储存传到curring函数中的除了fn之外的其他参数,并储存到args函数中
args = args.concat([].slice.call(arguments,));
return function () {
if (arguments.length === ) {
return fn.apply(this, args);
} else {
//将fn中的参数展开,然后再储存到args数组中
[].push.apply(args, arguments);
}
}
};
var cost = (function () {
var money = ;
return function () {
for (var i = , l = arguments.length; i < l; i++) {
money += arguments[i];
}
return money;
}
})();
var cost = currying(cost,,); // 转化成 currying 函数
cost(,); // 未真正求值
cost(); // 未真正求值
console.log((cost())); // 求值并输出:900

求值柯里化

  如果函数柯里化(curring)之后,传参的同时伴随着求值的过程,则代码简化如下

  var currying = function (fn) {
//获取除了fn之外的其他参数
var args = [].slice.call(arguments, );
return function () {
//获取fn里的所有参数
var innerArgs = [].slice.call(arguments);
//最终的参数列表为args和innerArgs的结合
var finalArgs = args.concat(innerArgs);
//将finalArgs里的参数展开,传到fn中执行
return fn.apply(null, finalArgs);
};
};
var cost = (function () {
var money = ;
return function () {
for (var i = , l = arguments.length; i < l; i++) {
money += arguments[i];
}
return money;
}
})();
var cost = currying(cost,,); // 转化成 currying 函数
cost();//100+200+300=600
cost(,);//(100+200+300)+(100+200+100+100)=1100

反柯里化

  Array.prototype上的方法原本只能用来操作array对象。但用call和apply可以把任意对象当作this传入某个方法,这样一来,方法中用到this的地方就不再局限于原来规定的对象,而是加以泛化并得到更广的适用性

  有没有办法把泛化this的过程提取出来呢?反柯里化(uncurrying)就是用来解决这个问题的。反柯里化主要用于扩大适用范围,创建一个应用范围更广的函数。使本来只有特定对象才适用的方法,扩展到更多的对象。

  uncurrying的话题来自JavaScript之父Brendan Eich在2011年发表的一篇文章。以下代码是 uncurrying 的实现方式之一:

Function.prototype.uncurrying = function () {
var _this = this;
return function() {
var obj = Array.prototype.shift.call( arguments );
return _this.apply( obj, arguments );
};
};

  另一种实现方法如下

Function.prototype.currying = function() {
var _this = this;
return function() {
return Function.prototype.call.apply(_this, arguments);
}
}

  最终是都把this.method转化成method(this,arg1,arg2....)以实现方法借用和this的泛化

  下面是一个让普通对象具备push方法的例子

 var push = Array.prototype.push.uncurrying(),
obj = {};
push(obj, 'first', 'two');
console.log(obj);
/*obj {
0 : "first",
1 : "two"
}*/

  通过uncurrying的方式,Array.prototype.push.call变成了一个通用的push函数。这样一来,push函数的作用就跟Array.prototype.push一样了,同样不仅仅局限于只能操作array对象。而对于使用者而言,调用push函数的方式也显得更加简洁和意图明了

  最后,再看一个例子

var toUpperCase = String.prototype.toUpperCase.uncurrying();
console.log(toUpperCase('avd')); // AVD
function AryUpper(ary) {
return ary.map(toUpperCase);
}
console.log(AryUpper(['a', 'b', 'c'])); // ["A", "B", "C"]

深入理解javascript函数进阶系列第二篇——函数柯里化的更多相关文章

  1. 深入理解javascript选择器API系列第二篇——getElementsByClassName

    × 目录 [1]使用 [2]classList [3]扩展 前面的话 既然有getElementById()和getElementsByTagName()方法,为什么没有getElementsByCl ...

  2. 深入理解javascript函数系列第二篇——函数参数

    × 目录 [1]arguments [2]内部属性 [3]函数重载[4]参数传递 前面的话 javascript函数的参数与大多数其他语言的函数的参数有所不同.函数不介意传递进来多少个参数,也不在乎传 ...

  3. 深入理解javascript函数进阶系列第一篇——高阶函数

    前面的话 前面的函数系列中介绍了函数的基础用法.从本文开始,将介绍javascript函数进阶系列,本文将详细介绍高阶函数 定义 高阶函数(higher-order function)指操作函数的函数 ...

  4. swift 学习(二)基础知识 (函数,闭包,ARC,柯里化,反射)

    函数 func x(a:Int, b:Int)  {}   func x(a:Int, b:Int) -> Void {}  func x(a:Int, b:Int) ->(Int,Int ...

  5. 深入理解DOM事件机制系列第二篇——事件处理程序

    × 目录 [1]HTML [2]DOM0级 [3]DOM2级[4]IE[5]总结 前面的话 事件处理程序又叫事件侦听器,实际上就是事件的绑定函数.事件发生时会执行函数中相应代码.事件处理程序有HTML ...

  6. 深入理解脚本化CSS系列第二篇——查询计算样式

    × 目录 [1]getComputedStyle [2]注意事项 [3]currentStyle[4]IE 前面的话 元素的渲染结果是多个CSS样式博弈后的最终结果,这也是CSS中的C(cascade ...

  7. 深入理解javascript选择器API系列第一篇——4种元素选择器

    × 目录 [1]id属性 [2]标签名 [3]name属性[4]all 前面的话 说到最常见的DOM应用,恐怕就要数取得特定的某个或某组元素的引用了.DOM定义了许多方式来选取元素,包括getElem ...

  8. 深入理解DOM事件类型系列第二篇——键盘事件

    × 目录 [1]类型 [2]顺序 [3]按键信息[4]应用 前面的话 鼠标和键盘是电脑端主要的输入设备,上篇介绍了鼠标事件,本文将详细介绍键盘事件 类型 键盘事件用来描述键盘行为,主要有keydown ...

  9. 浅析 JavaScript 中的 函数 uncurrying 反柯里化

    柯里化 柯里化又称部分求值,其含义是给函数分步传递参数,每次传递参数后部分应用参数,并返回一个更具体的函数接受剩下的参数,这中间可嵌套多层这样的接受部分参数函数,直至返回最后结果. 因此柯里化的过程是 ...

随机推荐

  1. Ubuntu16.04卸载opencv2.4.9并安装opencv3.2.0+contrib

    本文为作者原创,转载请注明出处(http://www.cnblogs.com/mar-q/)by 负赑屃 需要用到opencv中的surf和sift算法,机器上只有opencv3.2,没有扩展包,于是 ...

  2. 2-SAT问题总结

    2-SAT问题总结 2-SAT问题:n个布尔型的变量,给出m个约束条件,约束条件例如:A,B不能同时为真,A,B必须同时为真等. 看了算法入门经典中的解决办法,关于这种解决办法比较容易理解,并且效率也 ...

  3. poj 1759 Garland

    Garland Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 2365   Accepted: 1007 Descripti ...

  4. web实现数据交互的几种常见方式

    前言 在当今社会,作为一名前端程序猿,并不是一昧的去制作静态页面就可以满足滴:你说你会制作网页,好吧,只能说你算是一个前端程序猿.但这是你作为一个程序猿最基本的能力,并不会为你进行加分: 我们都明白, ...

  5. H5缓存

    h5 namifest(已经被废弃):http://www.tuicool.com/articles/VvABB3Y service worker缓存:https://developer.mozill ...

  6. filereader api 类型

    filereader类似XMLHttpRequest,只是它用来从文件系统读取文件,提供了不同的方法去读取文件数据:1.readAsText2.readAsDataURL3.readAsBinaryS ...

  7. HDU3045 Picnic Cows (斜率DP优化)(数形结合)

    转自PomeCat: "DP的斜率优化--对不必要的状态量进行抛弃,对不优的状态量进行搁置,使得在常数时间内找到最优解成为可能.斜率优化依靠的是数形结合的思想,通过将每个阶段和状态的答案反映 ...

  8. IntelliJ配置jenkins服务的Crumb Data

    近期在做jenkins测试,IntelliJ并没有自动安装jenkins服务器,因此需要自己添加,但是如果不配置Crumb Data,jenkins的服务就不能使用. 首先在服务器中开启CSRF服务, ...

  9. Java线程面试题

     1:什么是线程?    线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速.比如,如果一个线 ...

  10. [转载] java多线程学习-java.util.concurrent详解(四) BlockingQueue

    转载自http://janeky.iteye.com/blog/770671 ------------------------------------------------------------- ...