在Ruby中使用to_enum创建可枚举对象的优点是什么?

为什么要使用to_enum方法而不是直接使用对象,在Ruby中创建对象的代理引用? 我想不出任何实际用途,试图理解这个概念以及有人可能使用它的地方,但我看到的所有例子看起来都非常微不足道。

例如,为什么使用:

"hello".enum_for(:each_char).map {|c| c.succ } 

代替

 "hello".each_char.map {|c| c.succ } 

我知道这是一个非常简单的例子,有没有人有任何现实世界的例子?

大多数接受块的内置方法都会返回一个枚举器,以防止没有提供块(比如你的例子中的String#each_char )。 对于这些,没有理由使用to_enum ; 两者都会产生同样的效果。

但是,有些方法不会返回枚举器。 在这种情况下,您可能需要使用to_enum

 # How many elements are equal to their position in the array? [4, 1, 2, 0].to_enum(:count).each_with_index{|elem, index| elem == index} #=> 2 

另一个例子, Array#productArray#product #uniq#uniq! 没有用来接受一个块。 在1.9.2中,这已被更改,但为了保持兼容性,没有块的表单不能返回Enumerator 。 人们仍然可以“手动”使用to_enum来获取枚举器:

 require 'backports/1.9.2/array/product' # or use Ruby 1.9.2+ # to avoid generating a huge intermediary array: e = many_moves.to_enum(:product, many_responses) e.any? do |move, response| # some criteria end 

to_enum的主要用途是在实现自己的迭代方法时。 您通常会将第一行作为第一行:

 def my_each return to_enum :my_each unless block_given? # ... end 

我认为它与内部和外部迭代器有关。 当您返回这样的枚举器时:

 p = "hello".enum_for(:each_char) 

p是外部枚举器。 外部迭代器的一个优点是:

外部迭代器比内部迭代器更灵活。 例如,将两个集合与外部迭代器进行相等比较很容易,但内部迭代器实际上是不可能的…. 但另一方面,内部迭代器更易于使用,因为它们为您定义迭代逻辑。 [来自Ruby Programming Language一书,ch。 5.3]

所以,使用外部迭代器,你可以做到,例如:

 p = "hello".enum_for(:each_char) loop do puts p.next end 

假设我们想要获取一组键和一组值,并在Hash中将它们缝合起来:

使用#to_enum

 def hashify(k, v) keys = k.to_enum(:each) values = v.to_enum(:each) hash = [] loop do hash[keys.next] = values.next # No need to check for bounds, # as #next will raise a StopIteration which breaks from the loop end hash end 

没有#to_enum:

 def hashify(k, v) hash = [] keys.each_with_index do |key, index| break if index == values.length hash[key] = values[index] end hash end 

阅读第一种方法要容易得多,你不觉得吗? 不是很容易,但想象一下,如果我们以某种方式操纵3个数组中的项目? 5? 10?

这不是你的问题的答案,但希望它是相关的。

在第二个示例中,您调用each_char而不传递块。 当没有块each_char返回一个Enumerator所以你的例子实际上只是两种做同样事情的方法。 (即两者都会导致创建一个可枚举的对象。)

 irb(main):016:0> e1 = "hello".enum_for(:each_char) => # irb(main):017:0> e2 = "hello".each_char => # irb(main):018:0> e1.map { |c| c.succ } => ["i", "f", "m", "m", "p"] irb(main):019:0> e2.map { |c| c.succ } => ["i", "f", "m", "m", "p"] 

它适用于大型或无限的发电机对象。 例如,以下内容将为您提供整个Fibonacci序列的枚举器,从0到无穷大。

 def fib_sequence return to_enum(:fib_sequence) unless block_given? yield 0 yield 1 x,y, = 0, 1 loop { x,y = y,x+y; yield(y) } end 

to_enum有效地允许您以常规yields编写此内容,而无需使用Fiber s。

然后,您可以根据需要对其进行切片,并且内存效率非常高,因为内存中不会存储任何数组:

 module Slice def slice(range) return to_enum(:slice, range) unless block_given? start, finish = range.first, range.max + 1 copy = self.dup start.times { copy.next } (finish-start).times { yield copy.next } end end class Enumerator include Slice end fib_sequence.slice(0..10).to_a #=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] fib_sequence.slice(10..20).to_a #=> [55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]