深入探讨javascript函数式编程(3)

// 每个函数占用一行来调用,不如…… arr = [1, 2, 3, 4]; arr1 = arr.reverse(); arr2 = arr1.concat([5, 6]); arr3 = arr2.map(Math.sqrt); // ……把它们串到一起放在一行里面 console.log([1, 2, 3, 4].reverse().concat([5, 6]).map(Math.sqrt)); // 括号也许可以说明是怎么回事 console.log(((([1, 2, 3, 4]).reverse()).concat([5, 6])).map(Math.sqrt));

这只有在函数是目标对象所拥有的方法时才有效。如果你要创建自己的函数,比如要把两个数组zip到一起, 你必须把它声明为Array.prototype对象的成员.看一下下面的代码片段:
Array.prototype.zip = function(arr2) {
  // ...
}

这样我们就可以写成下面的样子
arr.zip([11,12,13,14).map(function(n){return n*2});
// Output: 2, 22, 4, 24, 6, 26, 8, 28

递归

递归应该是最著名的函数式编程技术。就是一个函数调用它自己。

当函数调用自己,有时奇怪的事情就发生了。它的表现即是一个循环,多次执行同样的代码,也是一个函数栈。

使用递归函数时必须十分小心地避免无限循环(这里应该说是无限递归)。就像循环一样,必须有个停止条件。 这叫做基准情形(base case)。

下面有个例子

var foo = function(n) { if (n < 0) { // 基准情形 return 'hello'; } else { // 递归情形 return foo(n - 1); } } console.log(foo(5));

译注:原文中的代码有误,递归情形的函数调用缺少return,导致函数执行得最后没有结果。这里已经纠正。

递归和循环可以相互转换。但是递归算法往往更合适,甚至是必要的,因为有些情形用循环很费劲。

一个明显的例子就是遍历树。

var getLeafs = function(node) { if (node.childNodes.length == 0) { // base case return node.innerText; } else { // recursive case: return node.childNodes.map(getLeafs); } }

分而治之

递归不只是代替for和while循环的有趣的方式。有个叫分而治之的算法,它递归地把问题拆分成更小的情形, 直到小到可以解决。

历史上有个欧几里得算法用于找出两个数的最大公分母

function gcd(a, b) { if (b == 0) { // 基准情形 (治) return a; } else { // 递归情形 (分) return gcd(b, a % b); } } console.log(gcd(12,8)); console.log(gcd(100,20)); gcb = (a, b) -> if b is 0 then a else gcb(b, a % b)

理论上来说,分而治之很牛逼,但是现实中有用吗?当然!用Javascript的函数对数组排序不是很好, 它不但替换了原数组,也就是说数据不是不变的,并且它还不够可靠、灵活。通过分而治之,我们可以做得更好。

全部的实现代码大概要40行,这里只展示伪代码:

var mergeSort = function(arr) { if (arr.length < 2) { // 基准情形: 只有0或1个元素的数组是不用排序的 return items; } else { // 递归情形: 把数组拆分、排序、合并 var middle = Math.floor(arr.length / 2); // 分 var left = mergeSort(arr.slice(0, middle)); var right = mergeSort(arr.slice(middle)); // 治 // merge是一个辅助函数,返回一个新数组,它将两个数组合并到一起 return merge(left, right); } }

译注:关于用分而治之的思路进行排序的一个更好的例子是快排,使用Javascript也只有13行代码。 具体请参考我以前的博文 《优雅的函数式编程语言》

惰性求值

惰性求值,也叫做非严格求值,它会按需调用并推迟执行,它是一种直到需要时才计算函数结果的求值策略, 这对函数式编程特别有用。比如有行代码是 x = func(),调用这个func()函数得到的返回值会赋值给x。 但是x等于什么一开始并不重要,直到需要用到x的时候。等到需要用x的时候才调用func()就是惰性求值。

这一策略可以让性能明显增强,特别是当使用方法链和数组这些函数式程序员最喜爱的程序流技术的时候。 惰性求值让人兴奋的一个优点是让无限序列成为可能。因为在它实在无法继续延迟之前,什么都不需要被真正计算出来。 它可以是这个样子:

// 理想化的JavaScript伪代码: var infinateNums = range(1 to infinity); var tenPrimes = infinateNums.getPrimeNumbers().first(10);

这为很多可能性敞开了大门,比如异步执行、并行计算、组合,这只列举了一点。

然而,还有个问题,Javascript本身并不支持惰性求值,也就是说存在让Javascript模拟惰性求值的函数库, 这是第三章的主题。

您可能感兴趣的文章:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wggwwz.html