枚举器的循环魔术(同时没有中断和无限循环)
Enumerator's loop magic (No break and no endless loop at the same time)
Enumerator.new 中的循环如何知道在哪里停止?
现在是更具描述性的示例。这里有两段代码,它们都是 return 相同的数组:[1,2,4,8]
。但是在第一个例子中 break
条件 loop
存在,当第二个例子以某种方式停止时。
示例 #1 break
def simple n
x = []
a = 1
i = 0
loop do
x << a
a *= 2
i += 1
break unless i < n # in this case condition for stop used
end
x
end
p simple(4)== [1,2,4,8]
示例 #2 "magical"
def enumer
Enumerator.new do |x|
a = 1
loop do # How do this loop know where to stop?
x << a
a *= 2
end
end
end
p enumer.take(4) == [1,2,4,8]
考虑以下因素:
enum = Enumerator.new do |x|
x << "hello"
x << "world"
end
enum.take(1)
#=> ["hello"]
enum.take(100)
#=> ["hello", "world"]
这是怎么回事?
嗯,产生的变量 x
是 Enumerator::Yielder
的一个实例。每当您在变量上调用 <<
或 yield
时,一个值将附加到最终结果数组。
enum.take(n)
表示 "try to collect up to n
values for this enumerable".
所以,回顾你原来的例子,我们有:
loop do
x << a
a *= 2
end
因为您在可枚举上调用了 take(4)
,如果收集了 4
项,Enumerator::Yielder
会立即知道 return。
...另一方面,如果您尝试 运行 例如enumer.to_a
然后循环 将 永远继续 - 因为它没有任何提前退出的条件!
根据我的发现,ruby 关于其工作原理的文档有点少;但是有 this helpful description of the behaviour in the source code:
/*
* call-seq:
* Enumerator.new(size = nil) { |yielder| ... }
* Enumerator.new(obj, method = :each, *args)
*
* Creates a new Enumerator object, which can be used as an
* Enumerable.
*
* In the first form, iteration is defined by the given block, in
* which a "yielder" object, given as block parameter, can be used to
* yield a value by calling the +yield+ method (aliased as +<<+):
*
* fib = Enumerator.new do |y|
* a = b = 1
* loop do
* y << a
* a, b = b, a + b
* end
* end
*
* p fib.take(10) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
*
* The optional parameter can be used to specify how to calculate the size
* in a lazy fashion (see Enumerator#size). It can either be a value or
* a callable object.
*
* In the second, deprecated, form, a generated Enumerator iterates over the
* given object using the given method with the given arguments passed.
*
* Use of this form is discouraged. Use Kernel#enum_for or Kernel#to_enum
* instead.
*
* e = Enumerator.new(ObjectSpace, :each_object)
* #-> ObjectSpace.enum_for(:each_object)
*
* e.select { |obj| obj.is_a?(Class) } #=> array of all classes
*
*/
Enumerator.new 中的循环如何知道在哪里停止?
现在是更具描述性的示例。这里有两段代码,它们都是 return 相同的数组:[1,2,4,8]
。但是在第一个例子中 break
条件 loop
存在,当第二个例子以某种方式停止时。
示例 #1 break
def simple n
x = []
a = 1
i = 0
loop do
x << a
a *= 2
i += 1
break unless i < n # in this case condition for stop used
end
x
end
p simple(4)== [1,2,4,8]
示例 #2 "magical"
def enumer
Enumerator.new do |x|
a = 1
loop do # How do this loop know where to stop?
x << a
a *= 2
end
end
end
p enumer.take(4) == [1,2,4,8]
考虑以下因素:
enum = Enumerator.new do |x|
x << "hello"
x << "world"
end
enum.take(1)
#=> ["hello"]
enum.take(100)
#=> ["hello", "world"]
这是怎么回事?
嗯,产生的变量 x
是 Enumerator::Yielder
的一个实例。每当您在变量上调用 <<
或 yield
时,一个值将附加到最终结果数组。
enum.take(n)
表示 "try to collect up to n
values for this enumerable".
所以,回顾你原来的例子,我们有:
loop do
x << a
a *= 2
end
因为您在可枚举上调用了 take(4)
,如果收集了 4
项,Enumerator::Yielder
会立即知道 return。
...另一方面,如果您尝试 运行 例如enumer.to_a
然后循环 将 永远继续 - 因为它没有任何提前退出的条件!
根据我的发现,ruby 关于其工作原理的文档有点少;但是有 this helpful description of the behaviour in the source code:
/*
* call-seq:
* Enumerator.new(size = nil) { |yielder| ... }
* Enumerator.new(obj, method = :each, *args)
*
* Creates a new Enumerator object, which can be used as an
* Enumerable.
*
* In the first form, iteration is defined by the given block, in
* which a "yielder" object, given as block parameter, can be used to
* yield a value by calling the +yield+ method (aliased as +<<+):
*
* fib = Enumerator.new do |y|
* a = b = 1
* loop do
* y << a
* a, b = b, a + b
* end
* end
*
* p fib.take(10) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
*
* The optional parameter can be used to specify how to calculate the size
* in a lazy fashion (see Enumerator#size). It can either be a value or
* a callable object.
*
* In the second, deprecated, form, a generated Enumerator iterates over the
* given object using the given method with the given arguments passed.
*
* Use of this form is discouraged. Use Kernel#enum_for or Kernel#to_enum
* instead.
*
* e = Enumerator.new(ObjectSpace, :each_object)
* #-> ObjectSpace.enum_for(:each_object)
*
* e.select { |obj| obj.is_a?(Class) } #=> array of all classes
*
*/