帮助理解 Ruby 中的产量和枚举器

Posted

技术标签:

【中文标题】帮助理解 Ruby 中的产量和枚举器【英文标题】:Help understanding yield and enumerators in Ruby 【发布时间】:2010-11-02 20:14:00 【问题描述】:

如果有人能帮助我理解在枚举器中使用 Yielder 与在枚举器中调用 yield 之间的区别,我将不胜感激。

“扎实的 Rubyist”暗示人们不会“从块中屈服”,但没有准确解释正在发生的事情。

谢谢

【问题讨论】:

“Yielder”是什么意思?我以前从未听说过这个词。 我也不明白其中的区别。 @Cloudhead:看这里blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/19918 +1 这个问题被不公平地标记了。 【参考方案1】:

如果您首先了解 yield 的工作原理,这可能会有所帮助。这是一个例子:

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 `<main>'

但这是一个有趣的枚举器,因为它不产生任何值——它只是执行一些代码(恰好打印一些输出),然后结束。该枚举器几乎等同于:

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 `<main>'

当枚举器无法产生值时,它会引发 StopIteration 异常。所以在这两种情况下,枚举器都无法产生值。

但我仍然不清楚“yielder”在做什么。它看起来 就像它正在收集所有计算值,以便它可以 稍后在您使用枚举器时反刍它们。如果那是 情况,那么它似乎只适用于“小” 序列....你不会想创建一个存储 50 的枚举器 百万物品。

没有。事实上,您可以创建一个产生无限数量值的枚举器。这是一个例子:

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

因为消息“just executed y y << val 行停止。因此,枚举器没有连续旋转 while 循环并将所有值插入到 y 中——即使语法与将值推送到数组中完全相同:arr &lt;&lt; val

y &lt;&lt; val 真正的意思是:当调用 e.next() 时产生这个值,然后在下一行继续执行。如果你在前面的例子中添加另一个 e.next,你会看到这个额外的输出:

just executed y << val
in while loop
2

当代码中遇到y &lt;&lt; val 时,执行总是停止。然后调用 e.next 会在右侧生成值,然后在下一行继续执行。

如果 ruby​​ 将 yielder 语句的语法如下:

y >> val

我们可以将其解释为:在这里停止执行,然后在调用 e.next 时产生 val。

David Black 建议不要使用 y.yield val 语法,它等同于 y &lt;&lt; val,以免读者认为它的工作方式类似于 yield 语句。 y.yield val应该解释为:“在这里停止执行,当调用next时产生val,然后在下一行继续执行。个人认为y &lt;&lt; val语法比y.yield val更突出,所以是更容易在代码中发现并轻松识别执行停止的位置。

【讨论】:

【参考方案2】:

好吧,除非我遗漏了什么,否则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 <main>': no block given (yield) (LocalJumpError)
        from yield.rb:19:in `each'
        from yield.rb:19:in `each'
        from yield.rb:19:in `<main>

当他说(第 304 页)“你不要这样做”时,他并不是说“这不是最好的方法”。他的意思是,“那行不通。”

编辑:但是,您可以通过这种方式显式调用 yield:

e = Enumerator.new do |y|
  y.yield 1
  y.yield 2
  y.yield 3
end

如果您发现说 yield&lt;&lt; 更明确或更清楚,那么就这样做。

第二次编辑:查看 David 的原始帖子和 Jorg 的更新答案,我认为这个问题最初存在混淆。 Jorg 认为 David 是在询问 Enumerator::Yielder#yieldEnumerator::Yielder::&lt;&lt; 之间的区别,但 David 不确定 The Well Grounded Rubyist 说“不要写 yield 1 等”是什么意思。我的回答适用于关于 The Well Grounded Rubyist 的问题。 (当我今天回顾这个帖子时,鉴于其他更新,我的答案看起来很奇怪。)

【讨论】:

是的,我发现 yield 不起作用,因为没有阻塞。但我仍然不清楚“屈服者”在做什么。看起来它正在收集所有计算值,以便以后使用枚举器时可以反刍它们。如果是这样的话,那么它似乎只适用于“小”序列......你不会想要一个存储 5000 万个项目的枚举器。【参考方案3】:

Enumerator::Yielder#yield 方法和Enumerator::Yielder::&lt;&lt; 方法完全相同。事实上,它们是别名。

因此,您使用这两者中的哪一个是 100% 个人偏好,就像 Enumerable#collectEnumerable#mapEnumerable#injectEnumerable#reduce

【讨论】:

好吧,我不确定我是否像关心实际发生的事情那样关心他的“想法”。它们是否相同?换句话说,就是 e = Enumerator.new do |y| y 不,显然它们不一样,因为第二个甚至不是有效的 Ruby。

以上是关于帮助理解 Ruby 中的产量和枚举器的主要内容,如果未能解决你的问题,请参考以下文章

使用辅助方法和 Ruby 可枚举返回 html 格式的对象

理解Ruby中的作用域

如何理解Python中的容器对象

ruby 创建一个接收N个枚举器的枚举器然后排序

如何在 Ruby 中“关闭”枚举器?

ruby Ruby函数使用枚举器来获取数字的倍数