在Ruby的函数式编程的第一部分,我们了解了基本的函数式编程,并且详细研究了不可变值和无副作用的代码。在第二部分,我们研究了高阶函数与柯里化(currying)的概念,同时也研究了Ruby中匿名函数的多种不同形态。
在这个系列的最后一部分中,我们将继续探究Ruby中的递归和惰性(实在无力),并且纯粹向你展示如何将这些函数式编程的要素应用到你日常的编码中去。
Ruby的各种循环
可能,你不是一个编程新手,你很熟悉常见的循环结构。循环的概念很早就出现在了一些编程书籍,并且它们都很好的释义了循环的概念:把一段代码重复执行N次。很早以前goto语句被使用在这种用途上,但是最终大多数的编程语言没用再使用goto进行循环,而是用了循环结构来保持代码的简洁与灵活性。
Ruby有几种不同的循环结构供你使用:
当然, 我们已经在第一部分介绍过折叠,那我们来看一些功能性递归。
调用自己(递归)
递归函数是指调用自己的函数。这种方式是用调用函数来代替标准循环结构的功能。它的原理是用一个新的参数来模拟对自己的迭代过程,并在参数符合一个约束后返回。
给出一个递归的例子,不使用传统的循环结构,来实现计算斐波那契数列(http://baike.baidu.com/view/816.htm)。
def fact(n) return 1 if (0..1).include?(n) n * fact(n - 1) end
如果让你来计算 6!,即6的阶乘,你可能会这么算 6*5*4*3*2*1 或者直接给出结果720,通过执行上边的代码,你也会得出同样的结果,下面是运行逻辑的拆解:
fact(6) = 6 * fact(5) = 6 * 5 * fact(4) = 6 * 5 * 4 * fact(3) = 6 * 5 * 4 * 3 * fact(2) = 6 * 5 * 4 * 3 * 2 * fact(1) = 6 * 5 * 4 * 3 * 2 * 1 = 720对于传统的循环结构,如此书写代码是多么简洁,这个循环体 也可以这么写:
def fact(n) factorial = 1 begin factorial *= n-- end while n > 1 factorial end这个例子使用了循环递归保持了循环状态的更新,同时也保持了简洁性和可读性。 不幸的是,需要警告大家,Ruby缺省的不支持尾调用优化( tail call optimisation) . 这意味着,我们上面计算斐波那契数列时用的函数递归最终会溢出堆栈。 你可以重新编译Ruby 1.9.x来支持尾调用优化, 所以,我们需要仔细检查是否这个特性开启,并抛出一个合适的异常来说明你的代码需要依赖尾函数优化,应该运行在支持这个特性的Ruby安装版本 枚举器(Enumerators): Ruby的生成器(Generators )
如果你可以拥有递归的威力,而不用担心尾递归优化,不用编译Ruby的定制版本,那会怎么样?
在很多语言中,都有生成器的概念,它混合了迭代子,或者循环和函数递归。在Ruby中,生成器叫“枚举器”。
Ruby 实际上试图强迫你在大多数循环形式中使用枚举器,可以例外是我上面使用的 (begin; ...; end while ...). 一直以来,当你使用#each, 你实际上使用了一个枚举器. 证据:
puts = [1, 2, 3].each.class # => Enumerator
枚举器,或者产生器,基于这样的想法,如果循环的每一遍重复都可以执行一个函数,循环的每一步执行都可以使用代码控制,那循环是多么威力强大啊。
在 Ruby里, 你可以使用Enumerator类的实例,在外面管理和遍历(iterate)循环 :
x = [1, 2, 3] enum = x.each puts enum.class # => Enumerator puts enum.next # => 1 puts enum.next # => 2 puts enum.next # => 3 puts enum.next # => StopIteration exception
你也可以写自定义的枚举器。这非常强大,允许你把任何陈旧的类转换成可以遍历的类。你只需要,包含Enumerable 模块到类里,然后定义一个each方法,这个方法每遍历一个值就yield一次。
Enumerable模块定义了一些方法你几乎每天都用;其中一些很流行包括:
这些方法,和 其他Enumerable模块中的部分 可以依照#each方法的方式定义。但单独使用#each方法也展示了枚举器相当可观的威力。
下面把我们之前用递归函数实现的斐波那契数列函数使用Enumerable 函数重写:
def fact(n) (1..n).inject(:*) || 1 end
真是惊人啊,这比我们之前的函数递归版明显可读性更强,更短啊。这也很好的适合了函数式风格:没有保存状态的变量,代码告诉Ruby做什么,而不是怎么做。
枚举器是大多数Ruby循环问题的解决方案,但是他们有个限制。如果说我们需要一个类产生无尽的质数列表。有一个应用,我们想找到前20个包含数字3的质数,另一个应用,我们想找到前10个质数-每个质数的每一位数字加起来的和也是质数。使用标准的Enumerable函数来写这些功能即使不是不可能,但是也是很费力。让我们看看惰性枚举器吧。
惰性枚举器
在Haskell之类的函数式语言,惰性计算(lazy evaluation) 是一个特性只在需要时才计算一个值。在Haskell, 这对于代码里无限长的列表很有效,在任意时刻,只产生这些列表需要的数据。在像Ruby这样严格计算的语言中,这不可能。Ruby会禅师计算整个无限的列表,最终耗尽进程中的内存。
对于新的Ruby 2.0增加了一个新的类Enumerator::Lazy 来支持枚举器额惰性计算。这意味着在Ruby中也可以处理无限长度列表的数据了。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。 2KB翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。2KB项目(www.2kb.com,源码交易平台),提供担保交易、源码交易、虚拟商品、在家创业、在线创业、任务交易、网站设计、软件设计、网络兼职、站长交易、域名交易、链接买卖、网站交易、广告买卖、站长培训、建站美工等服务