如何从Ruby中的Enumerator对象实际获取值?

Asa*_*svi 3 ruby enumerator

我对如何从Enumerator对象中获取值感兴趣.在下面的一段代码中,我期待第一次enum.next调用引发异常,因为enum在调用之后已经收到了所有值enum.to_a.

enum = Enumerator.new do |yielder|
  yielder.yield 1
  yielder.yield 2
  yielder.yield 3
end

p enum.to_a # => [1, 2, 3]

puts enum.next # Expected StopIteration here
puts enum.next
puts enum.next
puts enum.next # => StopIteration exception raised
Run Code Online (Sandbox Code Playgroud)

调用next与迭代器方法之间的区别to_aEnumerator什么?

Phi*_*ßen 6

简短回答:to_a总是遍历所有元素并且不会提升迭代器的位置.这就是为什么Enumerator#next将从第一个元素开始,即使你to_a之前已经调用过.调用to_a不会修改枚举器对象.


以下是详细信息:

术语:内部与外部迭代

在Ruby中讨论迭代器时,会出现两个术语:

  1. 内部迭代(也称为隐式迭代)
  2. 外部迭代

在您的问题中,enum.to_aenum用于内部迭代enum.next的示例,而是外部迭代的示例.

外部迭代提供更多控制,但是更低级别的操作.内部迭代通常更优雅.不同之处在于外部迭代使状态显式(当前位置),而内部迭代隐式应用于所有元素.

内部迭代:to_a

to_a将调用Enumerator#each,根据此Enumerator的构造方式迭代块.

这是关键点.因为它不对从中调用它的枚举器对象的内部状态(位置)进行操作,所以它不会干扰对next(外部迭代操作)的调用.

外部迭代:下一步

创建Enumerator对象时,其状态将初始化为指向第一个对象.您可以通过调用来修改内部状态next,这将提升位置.一旦消耗了所有元素,它将引发StopIteration异常.

请注意,只有在使用枚举器对象进行外部迭代时,状态才有意义.这就解释了为什么你可以安全地调用to_a已经消耗了所有元素的枚举器,它仍然会返回所有元素的列表.所有的内部迭代操作(例如,each,to_a,map`)不与外部迭代干扰.

在Rubinius实施

我查看了Rubinius源代码,了解它是如何在那里实现的.虽然它不是语言规范,但它应该与事实相对接近.切入点:

请注意,Enumerator包含Enumerable作为mixin.