我对如何从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_a
是Enumerator
什么?
简短回答:to_a
总是遍历所有元素并且不会提升迭代器的位置.这就是为什么Enumerator#next将从第一个元素开始,即使你to_a
之前已经调用过.调用to_a
不会修改枚举器对象.
以下是详细信息:
在Ruby中讨论迭代器时,会出现两个术语:
在您的问题中,enum.to_a
是enum
用于内部迭代enum.next
的示例,而是外部迭代的示例.
外部迭代提供更多控制,但是更低级别的操作.内部迭代通常更优雅.不同之处在于外部迭代使状态显式(当前位置),而内部迭代隐式应用于所有元素.
to_a
将调用Enumerator#each,根据此Enumerator的构造方式迭代块.
这是关键点.因为它不对从中调用它的枚举器对象的内部状态(位置)进行操作,所以它不会干扰对next
(外部迭代操作)的调用.
创建Enumerator对象时,其状态将初始化为指向第一个对象.您可以通过调用来修改内部状态next
,这将提升位置.一旦消耗了所有元素,它将引发StopIteration
异常.
请注意,只有在使用枚举器对象进行外部迭代时,状态才有意义.这就解释了为什么你可以安全地调用to_a
已经消耗了所有元素的枚举器,它仍然会返回所有元素的列表.所有的内部迭代操作(例如,each
,to_a,
map`)不与外部迭代干扰.
我查看了Rubinius源代码,了解它是如何在那里实现的.虽然它不是语言规范,但它应该与事实相对接近.切入点:
请注意,Enumerator包含Enumerable作为mixin.