帮助理解Ruby中的yield和枚举器
如果有人能帮我理解在枚举器中使用Yielder与在枚举器中调用yield之间的区别,我将不胜感激。
“有根据的Rubyist”表明,人们不会“从块中屈服”,但并不能准确地解释正在发生的事情。
谢谢
Enumerator::Yielder#yield
方法和Enumerator::Yielder::<<
方法完全相同 。 实际上,它们是别名。
所以,你使用的那两个中的哪一个是100%的个人偏好,就像Enumerable#collect
和Enumerable#map
或Enumerable#inject
和Enumerable#reduce
。
如果您首先了解收益率如何运作,它可能会有所帮 这是一个例子:
def do_stuff if block_given? yield 5 else 5 end end result = do_stuff {|x| x * 3 } puts result --output:-- 15
在do_stuff方法调用中:
do_stuff {|x| x * 3 }
..块就像一个函数,并传递给方法do_stuff。 在do_stuff中,yield调用函数并传递指定的参数 – 在本例中为5。
需要注意的一些重要事项:
-
在方法中调用yield
-
调用方法时,可以将块传递给方法
-
yield用于调用块。
好的,现在让我们来看看你的评论问题:
这是真的吗?
e = Enumerator.new do |y| y << 1 y << 2 y << 3 end
与...完全相同
e = Enumerator.new do #I think you forgot to write .new here yield 1 yield 2 yield 3 end
在第二个示例中,任何地方都没有方法定义 - 因此您无法调用yield。 错误! 因此,这两个例子并不相同。
但是,你可以这样做:
def do_stuff e = Enumerator.new do yield 1 yield 2 yield 3 end end my_enum = do_stuff {|x| puts x*3} my_enum.next --output:-- 3 6 9 1.rb:12:in `next': iteration reached an end (StopIteration) from 1.rb:12:in `'
但这是一个有趣的枚举器,因为它不会产生任何值 - 它只是执行一些代码(恰好打印一些输出),然后结束。 该枚举器几乎相当于:
def do_stuff e = Enumerator.new do end end my_enum = do_stuff my_enum.next --output:-- 1.rb:7:in `next': iteration reached an end (StopIteration) from 1.rb:7:in `'
当枚举器无法生成值时,会引发StopIterationexception。 因此,在这两种情况下,枚举器都无法生成值。
但我仍然不清楚“yielder”在做什么。 看起来它正在收集所有计算出的值,以便以后在使用枚举器时可以反刍它们。 如果是这种情况,那么它似乎只适用于“小”序列......你不会想要制作一个存储5000万件物品的枚举器。
不可以。实际上,您可以创建一个生成无限数量值的枚举器。 这是一个例子:
e = Enumerator.new do |y| val = 1 while true y << val val += 1 end end puts e.next puts e.next puts e.next --output:-- 1 2 3
添加一些调试消息应该具有洞察力:
e = Enumerator.new do |y| val = 1 while true puts "in while loop" y << val val += 1 end end puts e.next --output:-- in while loop 1
请注意,该消息仅打印一次。 所以事情正在发生并不明显:
e = Enumerator.new do |y| val = 1 while true puts "in while loop" y << val puts "just executed y << val" val += 1 end end puts e.next --output:-- in while loop 1
因为消息“刚执行y << val”没有出现在输出中,这意味着执行必须在行y << val
上停止。 因此,枚举器没有连续旋转while循环并将所有值都插入到y中 - 即使语法与将值推入数组完全相同: arr << val
。
y << val
意思是:当调用e.next()时产生这个值,然后继续执行下一行。 如果您在上一个示例中添加另一个e.next,您将看到以下附加输出:
just executed y << val in while loop 2
发生的事情是,当代码中遇到y << val
时,执行总是停止。 然后调用e.next在右侧生成值,然后在下一行继续执行。
如果ruby为yielder语句创建了这样的语法,那可能会更有意义:
y >> val
我们可以将其解释为含义:暂停执行,然后调用e.next时生成val。
David Black建议不要使用y.yield val
语法,这相当于y << val
以免读者认为它与yield语句的工作方式类似。 y.yield val
应该被解释为:“在这里停止执行,当next被调用时生成val,然后在下一行继续执行。就我个人而言,我认为语法y << val
比y.yield val
更y.yield val
,因此,更容易在代码中找到并轻松识别执行停止的位置。
好吧,除非我遗漏了某些东西,否则yield
的方法根本不起作用。 试试吧:
e = Enumerator.new do |y| y << 1 y << 2 y << 3 end f = Enumerator.new do yield 1 yield 2 yield 3 end e.each { |x| puts x } f.each { |x| puts x }
产生这个:
telemachus ~ $ ruby yield.rb 1 2 3 yield.rb:13:in `block in ': no block given (yield) (LocalJumpError) from yield.rb:19:in `each' from yield.rb:19:in `each' from yield.rb:19:in `
当他说(第304页)“你不这样做”时,他并不是说“这不是最好的办法”。 他的意思是,“那不行。”
编辑:但是,您可以通过以下方式显式调用yield:
e = Enumerator.new do |y| y.yield 1 y.yield 2 y.yield 3 end
如果你发现说yield
比<<
更明确或更清楚,那就这样做吧。
第二次编辑:看看大卫的原始post和Jorg的最新答案,我认为最初有关于这个问题的混淆。 Jorg认为大卫正在询问Enumerator::Yielder#yield
和Enumerator::Yielder::<<
之间的区别,但David不确定The Well Grounded Rubyist何时表示“不写yield 1
等”。 我的回答适用于关于The Well Grounded Rubyist的问题。 (当我今天回顾这个post时,根据其他更新,我的答案看起来很奇怪。)