【问题标题】:Ruby Block statements and Implicit ReturnsRuby 块语句和隐式返回
【发布时间】:2013-03-03 15:36:31
【问题描述】:

我一直认为 ruby​​ 爱好者选择在 ruby​​ 中隐含返回是因为风格偏好(更少的文字 = 更简洁)。但是,有人可以向我确认,在下面的示例中,您实际上必须使返回隐式,否则预期的功能将不起作用? (预期的功能是能够将句子拆分为单词并为每个单词返回“以元音开头”或“以辅音开头”)

# With Implicit Returns
def begins_with_vowel_or_consonant(words)
  words_array = words.split(" ").map do |word|
    if "aeiou".include?(word[0,1])
      "Begins with a vowel" # => This is an implicit return
    else
      "Begins with a consonant" # => This is another implicit return
    end
  end
end

# With Explicit Returns
def begins_with_vowel_or_consonant(words)
  words_array = words.split(" ").map do |word|
    if "aeiou".include?(word[0,1])
      return "Begins with a vowel" # => This is an explicit return
    else
      return "Begins with a consonant" # => This is another explicit return
    end
  end
end

现在,我知道肯定有很多方法可以使这段代码更高效、更好,但我这样安排的原因是为了说明隐式返回的必要性。有人可以向我确认确实需要隐式回报,而不仅仅是一种风格选择吗?

编辑: 这是一个示例来说明我要展示的内容:

# Implicit Return
begins_with_vowel_or_consonant("hello world") # => ["Begins with a consonant", "Begins with a consonant"] 

# Explicit Return
begins_with_vowel_or_consonant("hello world") # => "Begins with a consonant" 

【问题讨论】:

  • 很少有关于隐式/隐式回报性能比较的博客 - blog.nerdbucket.com/…。还有一个 - tomafro.net/2009/08/the-cost-of-explicit-returns-in-ruby
  • 您能准确定义什么“行不通”吗?隐式返回的例子对我来说很好。
  • 很抱歉造成混淆,但带有隐式返回的代码确实有效。我实际上是在说具有显式返回的代码是不能按预期工作的代码。我试图了解这是否是为什么实际上需要隐式返回的用例(因为我一直认为隐式返回是一种风格选择)
  • @SrikanthVenugopalan 感谢您提供的链接,尽管我不太关心隐式与显式返回的性能因素。相反,我更关心 ruby​​ 中是否存在隐式返回的实际用例——如果有,我的示例是否是其中之一。
  • 当您甚至不使用生成的words_array 时,为什么还要使用map 而不是each

标签: ruby return block


【解决方案1】:

方法的隐式返回值是方法中计算的最后一个表达式。

在您的情况下,您注释的两行中的任何一个都不是最后一个表达式。最后一个被求值的表达式是对words_array 的赋值(顺便说一句,这是完全没用的,因为它是最后一个表达式,之后无法使用该变量)。

现在,赋值表达式的值是多少?它是被分配的值,在这种特殊情况下,是map 方法的返回值,即Array。所以,这就是方法返回的内容。

second 示例中,在 map 的第一次迭代中,您将点击两个 returns 中的一个,从而立即从该方法返回。但是,在第一个示例中,您将始终遍历整个 words 数组。

问题是隐式和显式返回是不同的,问题是你声称是隐式返回的两行不是。

【讨论】:

  • 顺便说一句:您注释的两行隐含nexts,即隐含返回值。如果你在那里明确地输入next,你应该看不出有什么区别。
  • 啊,我明白你接下来的意思了!没有意识到你也可以传递下一个值。感谢您的提醒!
【解决方案2】:

发生这种情况的原因是 return 语句位于块内。如果 return 语句仅在函数内部,则执行流程将如您所愿。 ruby 中的块和返回(以及与此相关的中断语句)是一种奇怪的野兽。让我们举一个更简单的例子来捕捉你的问题:

def no_return()
  (1..10).each do |i|
    puts i
  end
  puts 'end'
end

def yes_return()
  (1..10).each do |i|
    puts i
    return 
  end
  puts 'end'
end

如果你同时运行这两个函数,你会看到第一个函数会打印出你所期望的数字 1-10 和单词“end”,但第二个函数只打印出 1。这是因为 ruby​​ 内部将返回(和中断语句)实现为异常。当这些异常被抛出时,一个叫做“catch table”的查找表被用来尝试找到执行流程应该继续的地方。如果没有找到,ruby 将在内部搜索 rb_control_frame 结构的堆栈,寻找指向我们正在执行的代码的指针。因此,在您的情况下,抛出异常并且最近的指针(就堆栈帧而言)位于方法的末尾,基本上导致整个方法终止。这就是为什么您甚至看不到“结束”被打印出来的原因。

no_return 指令:

0000 trace            1                                               (   3)
0002 putnil           
0003 getdynamic       i, 0
0006 send             :puts, 1, nil, 8, <ic:0>
0012 leave    

yes_return 指令:

0000 trace            1                                               (   3)
0002 putnil           
0003 getdynamic       i, 0
0006 send             :puts, 1, nil, 8, <ic:0>
0012 pop              
0013 trace            1                                               (   4)
0015 putnil           
0016 throw            1
0018 leave 

重要的是在 yes_return 指令中,“throw 1”实际上是作为抛出(并捕获)异常实现的 return 语句。除了“抛出 2”之外,中断的工作方式相同。

要亲自查看所有框架和捕获表的实际说明,请查看我不久前整理的这个应用程序: http://rubybytes.herokuapp.com/

有关更多信息,请查看 Pat Shaughnessy 的博客,尤其是此条目以获取更多信息: http://patshaughnessy.net/2012/6/29/how-ruby-executes-your-code

还有一个无耻的插件,如果你喜欢 java,请查看我的 ruby​​bytes 的 java 版本 javabytes.herokuapp.com 查看反汇编的 java 字节码(抱歉,它是无链接的,您必须将 url 复制并粘贴到浏览器中)。

【讨论】:

    【解决方案3】:

    return 将从产生块的方法返回,因此您需要隐式或使用 next

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-02-20
      • 1970-01-01
      • 1970-01-01
      • 2017-11-04
      • 2014-05-11
      • 1970-01-01
      • 2010-11-07
      • 2017-05-15
      相关资源
      最近更新 更多