【问题标题】:Ruby: the yield inside of a blockRuby:块内的产量
【发布时间】:2014-08-01 03:18:49
【问题描述】:

以下是扫描示例:

"abcdeabcabc".scan("a")

所以它返回一个由 3 个 a 组成的数组。另一个扫描示例:

"abcdeabcabc".scan("a") {|x| puts x}

它只是输出每个“a”,但仍然输出一个数组,而这一次它实际上是它返回的原始字符串。

所以从文档和上面的行为来看,扫描要么返回一个数组(没有给出块),要么返回原始字符串,在该字符串之前会发生一些副作用。关键是这两种情况都会返回一些东西。

那么如果我在块内放置一个“yield”会发生什么?什么会被退回?或者,没有? 返回值的类型是什么?

"abcdeabcabc".scan("a") {|x| yield x}

上述方法不起作用,因为 Ruby 抱怨没有给出块。这对我来说有点道理。但是,如果它是类方法的一部分,例如,自我实现的“每个”,那么以下工作:

class Test
  def my_each
    "abcdeabcabc".scan("a") {|x| yield x}
  end
end
# => :my_each

t = Test.new
# => #<Test:0x007ff00a8d79b0>

t.my_each {|x| puts "so this is #{x}"}
# it works. Outpus 3 a's then return the original string.

那么,Test类的my_each方法的返回值是多少呢?那是产量清单还是什么?但正如之前讨论的 "abcdeabcabc".scan("a") {|x| Ruby 会抱怨 yield x} 段,直到给出一个块。内部发生了什么将 my_each 块提供给 my_each 实现内部的段?

【问题讨论】:

    标签: ruby return yield


    【解决方案1】:

    块的传递与该函数的参数类似。这可以明确指定,如下所示:

    class Test
      def my_each(&block)
        "abcdeabcabc".scan("a") do |x|
          puts "!!! block"
          yield x
          # Could be replaced with: block.call(x)
        end
      end
    end
    

    从技术上讲,它完全一样(puts 放在那里以进行澄清),它的存在不会像通常用于参数的方式那样被检查。如果您忘记给它一个块,该函数将在第一个 yield 处停止,它必须以完全相同的 LocalJumpError 执行(至少,这就是我在 Rubinius 上得到的)。但是,在它发生之前请注意控制台中的“!!! block”。

    这样做是有原因的。您可以使用if block 检查您的函数是否被赋予一个块,如果它如上所述明确指定,然后跳过yields。一个很好的例子是 Rails 的 content_tag 助手。这个助手的调用可以是块嵌套的。一个简单的例子:

    content_tag :div do
      content_tag :div
    end
    

    ...产生如下输出:

    <div>
      <div></div>
    </div>
    

    因此,该块在您的方法的“顶部”(就调用堆栈而言)执行。每次yield 作为块上的某种函数调用时都会调用它。它不会在任何地方累积到之后执行该块。

    更新:

    许多eaches 返回的Enumerator 是由许多迭代器显式构造的,以保存应该发生的情况的上下文。

    可以在my_each上这样实现:

    class Test
      def my_each(&block)
        if block
          "abcdeabcabc".scan("a") { |x| yield x }
        else
          Enumerator.new(self, :my_each)
        end
      end
    end
    

    【讨论】:

    • 这个答案对我来说更有意义。在运行时, puts 实际上输出了一些东西,所以很高兴知道它以某种方式执行,但与其他答案一样,如果没有给出块,我仍然对返回值的类型感到好奇。你介意跟我分享一些这方面的信息吗?谢谢!
    • @Bruce 在第一个块执行之前没有返回。但是,正如您所指出的,许多没有块的eaches 返回一个Enumerator。这是明确完成的:如果没有给出块,则构造一个带有上下文的Enumerator 以重现该each:一个对象、一个迭代器方法及其参数。在 irb 中尝试:e = 1.upto(5)。检查e
    • 所以这可能是静态类型语言和动态类型语言之间的区别之一。因此,如果我理解正确,在第一个块执行之前不会发生类型推断。如果没有给出块并且给出了 Enumerator 的构造,则返回 Enumerator 类型的值;否则,如果给定了块,则进入该块并推断 my_each 的最后一个表达式的类型。那么 my_each 的最后一个表达式是什么?
    • @Brude 我编辑了答案以反映Enumerators 的性质。
    • @Bruce,再次将块视为参数,某种匿名函数。带有块的方法也会返回一些东西。对于scan,它是它处理的原始字符串。如果存在块,在我的示例中,这将是最后一个表达式。否则,它将是一个枚举器。是的,返回类型并不清楚,但由于差异只会在不同的使用场景中可见,这是最好的。
    【解决方案2】:

    由于给scan一个块,返回原始字符串。在块内做了什么并不重要。

    【讨论】:

    • 我知道 t.my_each {block} 最终会返回原始字符串,但我问的是 my_each 本身呢?我的意思是,在给它一个块之前。
    • my_each 与其参数和潜在块一起评估并取决于其参数。在给出块之前没有评估 my_each 之类的东西
    • 这正是令人困惑的部分。所以这意味着Ruby允许在类方法实现中“部分有效”的代码?由于 my_each 本身没有指定任何参数,也没有强制使用隐式最后一个参数(&block),因此该类方法的用户必须知道实现细节(有一个“yield”)才能使用该方法正确吗?出于封装的原因,这听起来不是一个好的设计。
    【解决方案3】:

    "abcdeabcabc".scan("a") {|x| yield x}

    在上述情况下,#scan每个匹配的字符传递给与其关联的块

    现在在#scan 的块内,您正在调用yield,它实际上随后调用了您传递给方法my_each 的块。 x 的值将传递给您通过方法 my_each 调用传递的块。

    太简单了,没有混淆。

    Test类的my_each方法的返回值是多少?

    根据您当前的代码,返回值应该是#scan方法返回值,这反过来会导致关联块的最后一条语句的结果使用方法#my_each(如果调用)或调用方法#scan接收器

    那是收益列表还是什么?

    是的,yield 将被调用,因为#scan 方法将找到许多匹配项。

    考虑下面的例子:-

    "abcdeabcabc".scan("w") {|x| yield x}
    

    这里不会调用与方法#scan 关联的,因为#scan 没有找到任何匹配项,这就是为什么yield 也不会被调用并且作为result 方法#my_each 不会输出块表达式(与方法一起传递)结果,而是"abcdeabcabc"

    【讨论】:

    • 感谢您的程序说明。也许是因为我刚从类 Java 语言的背景转换,我被困在返回值上,因为 Ruby 声称每个方法调用都会返回一些东西。我感到困惑的部分是 my_each 的调用返回的内容。和“each”一样,[1, 2, 4, 5].each 如果没有给出块,则返回一个枚举器。这里的“my_each”怎么样?如果没有给出块,它会返回什么?或者由于某种原因它是简单的无效?
    • 酷,我现在看到你的更新了。你的最后一个例子很有趣,谢谢你。在中间你说“是的,yield 将被称为......”,但这仍然是一个 yield 列表吗?那个“产量清单”是什么类型的?由于 Ruby 是动态类型的,我会对 Ruby 将在 "abcdeabcabc".scan("a") {|x| 的返回值中推断出什么类型感兴趣。产量 x}
    猜你喜欢
    • 2012-03-29
    • 2011-03-05
    • 2017-01-01
    • 2022-01-17
    • 1970-01-01
    • 2015-03-01
    • 2016-06-08
    • 2010-12-08
    • 2016-05-22
    相关资源
    最近更新 更多