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

发布于 2024-07-23 10:57:46 字数 130 浏览 8 评论 0 原文

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

“接地气的 Rubyist”表明人们不会“从区块中屈服”,但没有准确解释发生了什么。

谢谢

I would appreciate it if someone could help me understand the difference between using a Yielder in an Enumerator vs. just invoking yield in an Enumerator.

The "Well-grounded Rubyist" suggests that one doesn't "yield from the block" but doesn't explain precisely what's going on.

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

清醇 2024-07-30 10:57:46

如果您首先了解产量的工作原理可能会有所帮助。 下面是一个例子:

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.

需要注意的一些重要事项:

  1. yield 在方法内部被调用

  2. 当你调用一个方法时,你可以向该方法传递一个块

  3. yield 用于调用块。

好的,现在我们来看看您的评论问题:

这是真的吗

e = Enumerator.new do |y|  
    y <<   1  
    y <<   2  
    y <<   3  
  结尾  
  

完全相同

e = Enumerator.new do #我想你忘了在这里写.new 
      产量 1  
      产量 2  
      产量 3  
  结尾 
  

,因为任何地方都没有方法定义——所以你不能调用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

因为消息“justexecute y << val”没有出现在输出中,这意味着执行必须在 y << 行停止。 值。 因此,枚举器不会连续旋转 while 循环并将所有值插入到 y 中——即使语法与将值推入数组完全相同:arr << 值。

什么 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 << 最有价值的读者认为它的工作原理与yield 语句类似。 y.yield val 应该解释为:“在这里停止执行,当next调用product val时,则在下一行继续执行。我个人认为语法y < 比 y.yield val 更突出,因此更容易在代码中发现并轻松识别执行停止的位置。

It might help if you first understand how yield works. Here is an example:

def do_stuff
  if block_given?
    yield 5
  else
    5
  end
end

result = do_stuff {|x| x * 3 }
puts result

--output:--
15

In the the do_stuff method call:

do_stuff {|x| x * 3 }

..the block is like a function, and it is passed to the method do_stuff. Inside do_stuff, yield calls the function and passes the specified arguments--in this case 5.

Some important things to note:

  1. yield is called inside a method

  2. When you call a method, you can pass a block to the method

  3. yield is used to call the block.

Okay, now let's look at your comment question:

Is it true that

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

is exactly the same as

e = Enumerator.new do   #I think you forgot to write .new here
    yield 1 
    yield 2 
    yield 3 
end

In the second example, there is no method definition anywhere--so you can't call yield. Error! Therefore, the two examples are not the same.

However, you could do this:

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

But that is a funny enumerator because it doesn't produce any values--it just executes some code(which happens to print some output), then ends. That enumerator is almost equivalent to:

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

When an enumerator cannot produce a value, it raises a StopIteration exception. So in both cases, the enumerator couldn't produce a value.

But it's still not clear to me what the "yielder" is doing. It looks
like it is collecting all the calculated values so that it can
regurgitate them later when you use the enumerator. If that's the
case, then it seems like it would only be practical for "small"
sequences....you wouldn't want to make an enumerator that stored 50
million items away.

No. In fact, you can create an enumerator that produces an infinite number of values. Here is an example:

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

Adding some debugging messages should prove insightful:

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

Note that the message only printed once. So something is going on that is not obvious:

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

Because the message "just executed y << val" does not show up in the output, that means execution must have halted on the line y << val. Therefore, the enumerator did not continuously spin the while loop and insert all the values into y--even though the syntax is exactly the same as pushing values into an array: arr << val.

What y << val really means is: when e.next() is called produce this value, then continue execution on the next line. If you add another e.next to the previous example, you will see this additional output:

just executed y << val
in while loop
2

What's happening is that execution always halts when y << val is encountered in the code. Then calling e.next produces the value on the right side, then execution continues on the next line.

It would probably have made more sense if ruby had made the syntax for the yielder statement like this:

y >> val

And we could interpret that as meaning: halt execution here, then when e.next is called produce val.

David Black recommends not using the y.yield val syntax, which is equivalent to y << val lest readers think it works similarly to the yield statement. y.yield val should be interpreted as: "stop execution here, and when next is called produce val, then continue execution on the next line. Personally, I think that the syntax y << val stands out more than y.yield val, so it is easier to spot in the code and readily identify where execution halts.

云之铃。 2024-07-30 10:57:46

好吧,除非我遗漏了什么,否则 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<<更明确或更清晰,那么就这样做。

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

Well, unless I'm missing something, the method with yield simply doesn't work. Try it:

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 }

Which produces this:

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>

When he says (page 304) "you don't do this," he doesn't mean "it's not the best way to do it." He means, "that won't work."

Edit: You can, however, call yield explicitly this way:

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

If you find saying yield more explicit or clearer than <<, then do it that way.

Second edit: Looking at David's original post and Jorg's updated answer, I think that there was a confusion originally about the question. Jorg thought David was asking about the difference between Enumerator::Yielder#yield and Enumerator::Yielder::<<, but David wasn't sure what The Well Grounded Rubyist means when it says "don't write yield 1 etc." My answer applies to the question about The Well Grounded Rubyist. (When I looked this thread back over today, my answer looked odd in the light of other updates.)

把回忆走一遍 2024-07-30 10:57:46

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

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

The Enumerator::Yielder#yield method and the Enumerator::Yielder::<< method are exactly the same. In fact, they are aliases.

So, which one of those two you use, is 100% personal preference, just like Enumerable#collect and Enumerable#map or Enumerable#inject and Enumerable#reduce.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文