深入剖析JavaScript中的函数currying柯里化【官方澳门新永利下载】

Currying柯里化是函数式语言都有的一个特性,如Perl,Python,JavaScript。本篇就借用一下JavaScript,介绍一下柯里化的思想及应用。

curry化来源与数学家 Haskell Curry的名字 (编程语言
Haskell也是以他的名字命名)。
 
柯里化通常也称部分求值,其含义是给函数分步传递参数,每次传递参数后部分应用参数,并返回一个更具体的函数接受剩下的参数,这中间可嵌套多层这样的接受部分参数函数,直至返回最后结果。

假设函数库里提供这样一个拼接URL地址的函数:

因此柯里化的过程是逐步传参,逐步缩小函数的适用范围,逐步求解的过程。 

function simpleURL(protocol, domain, path) { return protocol + "://" + domain + "/" + path;}simpleURL('http','www.jackzxl.net', 'index.html'); //http://www.jackzxl.net/index.html

柯里化一个求和函数
按照分步求值,我们看一个简单的例子

这是个最普通的函数毫无新意。但对于你的站点来说,第一个参数固定为http,第二个参数固定为www.jackzxl.net,唯一需要改变的是第三个参数。即你的站点中的任何页面或资源,前两个参数永远固定,只需要改变第三个参数。

var concat3Words = function (a, b, c) { 
  return a+b+c; 
}; 

var concat3WordsCurrying = function(a) { 
  return function (b) { 
    return function (c) { 
      return a+b+c; 
    }; 
  }; 
}; 
console.log(concat3Words("foo ","bar ","baza"));      // foo bar baza 
console.log(concat3WordsCurrying("foo "));         // [Function] 
console.log(concat3WordsCurrying("foo ")("bar ")("baza")); // foo bar baza 

显然你不想每次调用时都手动敲一下前两个参数,麻烦不说,还容易出错。怎么办呢?你会想直接将库函数改成单参不就行了?

可以看到, concat3WordsCurrying(“foo “) 是一个
Function,每次调用都返回一个新的函数,该函数接受另一个调用,然后又返回一个新的函数,直至最后返回结果,分布求解,层层递进。(PS:这里利用了闭包的特点) 
 

function simpleURL { return "http://www.jackzxl.net/" + path;}

那么现在我们更进一步,如果要求可传递的参数不止3个,可以传任意多个参数,当不传参数时输出结果? 

这样改有两个问题,首先如果该库函数还需要被其他人或其他地方使用,直接改库函数这条路是绝对行不通的。其次就算你对函数有绝对的控制权,这样改显得也非常的不灵活,如果哪天你的站点要加上SSL呢?总不能把第一个参数再放回去吧。因此你正确的选择是柯里化。

首先来个普通的实现:

所谓柯里化就是:将函数与其参数的一个子集绑定起来后返回个新函数。如果感觉比较抽象,可以做一些类比,比如C++模板里的偏特化,这样理解起来能容易点。将上例柯里化一下:

var add = function(items){ 
  return items.reduce(function(a,b){ 
    return a+b 
  }); 
}; 
console.log(add([1,2,3,4])); 
var myURL = simpleURL.bind(null, 'http', 'www.jackzxl.net');myURL('myfile.js'); //http://www.jackzxl.net/myfile.js//站点加上SSLvar mySslURL = simpleURL.bind(null, 'https', 'www.jackzxl.net');mySslURL('myfile.js'); //https://www.jackzxl.net/myfile.js

但如果要求把每个数乘以10之后再相加,那么:

上述代码用bind来实现柯里化。再回过头体会一下柯里化定义:将函数与其参数的一个子集绑定起来后返回个新函数。柯里化后发现函数变得更灵活,更流畅,是一种简洁的实现函数委托的方式

var add = function (items,multi) { 
  return items.map(function (item) { 
    return item*multi; 
  }).reduce(function (a, b) { 
    return a + b 
  }); 
}; 
console.log(add([1, 2, 3, 4],10)); 

为何用bind来实现柯里化呢?因为简单嘛,有现成的就不必自己造轮子了。但因为本篇介绍的是柯里化,所以我们自己实现一下柯里化,来加深理解。它需要满足两点:参数子集,返回新函数:

好在有 map 和 reduce
函数,假如按照这个模式,现在要把每项加1,再汇总,那么我们需要更换map中的函数。 

var currying = function { var args = [].slice.call(arguments, 1); return function() { var newArgs = args.concat([].slice.call(arguments)); return fn.apply(null, newArgs); };};var myURL2 = currying(simpleURL, 'https', 'www.jackzxl.net');myURL2('myfile.js'); //http://www.jackzxl.net/myfile.js

下面看一下柯里化实现:

效果和用bind是一样的,我们仔细分析一下自定义的currying函数,首先参数fn是需要柯里化的simpleURL函数,后面均为可变参数(函数的arguments可参考这里),currying里每行代码的执行结果如下:

var adder = function () { 
  var _args = []; 
  return function () { 
    if (arguments.length === 0) { 
      return _args.reduce(function (a, b) { 
        return a + b; 
      }); 
    } 
    [].push.apply(_args, [].slice.call(arguments)); 
    return arguments.callee; 
  } 
};   
var sum = adder(); 

console.log(sum);   // Function 

sum(100,200)(300);  // 调用形式灵活,一次调用可输入一个或者多个参数,并且支持链式调用 
sum(400); 
console.log(sum());  // 1000 (加总计算) 
var currying = function { var args = [].slice.call(arguments, 1); //args为["https", "www.jackzxl.net"] return function() { var newArgs = args.concat([].slice.call(arguments)); //newArgs为["https", "www.jackzxl.net", "myFile.js"] return fn.apply(null, newArgs); //相当于return simpleURL("https", "www.jackzxl.net", "myFile.js"); };};

上面
adder是柯里化了的函数,它返回一个新的函数,新的函数接收可分批次接受新的参数,延迟到最后一次计算。 
 

上面已经说明了柯里化的原理和实现。那究竟柯里化有什么作用呢?常见的作用是:

通用的柯里化函数

  • 官方澳门新永利下载,参数复用
  • 延迟运行
  • 扁平化

更典型的柯里化会把最后一次的计算封装进一个函数中,再把这个函数作为参数传入柯里化函数,这样即清晰,又灵活。

参数复用上面例子已经展示了,不赘述。

例如 每项乘以10, 我们可以把处理函数作为参数传入:

发表评论

电子邮件地址不会被公开。 必填项已用*标注