【问题标题】:Undefined local variable based on syntax in Ruby基于Ruby语法的未定义局部变量
【发布时间】:2014-09-13 01:58:43
【问题描述】:

在以下 Ruby 代码中,

#! /usr/bin/env ruby
x = true
y = x and z = y
puts "z: #{z}"

它将按预期输出z: true

但在以下一个中,我希望具有相同的行为:

#! /usr/bin/env ruby
x = true
z = y if y = x
puts "z: #{z}"

结果

main:Object (NameError) 的未定义局部变量或方法“y”

这是为什么呢?

我明白我正在做一个赋值,并隐式检查赋值值以确定是否运行z = y。我也明白,如果我在x = 5 行之后添加y 的声明y = nil,它将按预期通过并运行。

但是,期望语言首先评估 if 部分,然后评估其内容,第二块代码的行为与第一块代码相同,这难道不正确吗?

【问题讨论】:

  • 这是一个非常好的问题,因为z = 2 if y = x 可以正常工作,z = w if false 也可以...
  • 这是 Ruby 的愚蠢领域之一。有一些定义不明确的子句相当于“如果解析器遇到分配”(意味着分配确实 not 必须执行以创建本地绑定).. 除非它在某些情况下崩溃像这样,还有其他几个。参考。 stackoverflow.com/questions/25783428/…
  • @MarcBaumbach 这不是“错误”。这是“预期的行为”,即使不是真的......预期的。
  • 我认为那些导致 x=x 不是 NameError 的小鬼也有自己的爪子。
  • @roippi x=x 不是错误,因为分配 x = 在调用 x 之前。

标签: ruby


【解决方案1】:

TL;DR

这实际上是特定于解释器的。该问题出现在 MRI Ruby 2.1.2 和 JRuby 1.7.13 中,但在 Rubinius 中可以正常工作。例如,使用 Rubinius 2.2.10:

x = true
z = y if y = x
#=> true

在 MRI 中,对 Ripper 的一些探索表明,尽管 AST 的分配相似,Ruby 对后置条件的处理方式不同。在构建 AST 时,它实际上对后置条件使用了不同的标记,这似乎对赋值表达式的评估顺序有影响。是否应该这样,或者是否可以修复,是Ruby Core Team的问题。

为什么它与逻辑与一起工作

x = true
y = x and z = y

这成功了,因为它实际上是按顺序进行的两个分配,因为true 被分配给 x,因此评估为真。由于第一个表达式为真,由逻辑 连接的下一个表达式也被计算并且同样计算为真。

y = x
#=> true

z = y
#=> true

换句话说,x 被赋值为true,然后z 也被赋值为true。任何一个赋值的右侧都没有未定义。

为什么它会因后置条件而失败

x = true
z = y if y = x

在这种情况下,实际上是先评估后置条件。您可以通过查看AST 看到这一点:

require 'pp'
require 'ripper'

x = true

pp Ripper.sexp 'z = y if y = x'
[:program,
 [[:if_mod,
   [:assign,
    [:var_field, [:@ident, "y", [1, 9]]],
    [:vcall, [:@ident, "x", [1, 13]]]],
   [:assign,
    [:var_field, [:@ident, "z", [1, 0]]],
    [:vcall, [:@ident, "y", [1, 4]]]]]]]

与您的第一个示例不同,其中 y 在第一个表达式中分配了 true,因此在分配给 z 之前在第二个表达式中解析为 true ,在这种情况下,y 在未定义的情况下被评估。这会引发NameError

当然,人们可以合理地争辩说这两个表达式都包含赋值,并且如果 Ruby 的解析器首先评估 y = x 就像使用普通 if 语句一样,那么 y 就不会真的是未定义的(参见AST 下面)。这可能只是后置条件 if 语句和 Ruby 处理 :if_mod 标记的方式的一个怪癖。

使用 :if 而不是 :if_mod 令牌成功

如果你反转逻辑并使用正常的 if 语句,它就可以正常工作:

x = true
if y = x
  z = y
end
#=> true

查看 Ripper 会产生以下 AST:

require 'pp'
require 'ripper'

x = true

pp Ripper.sexp 'if y = x; z = y; end'
[:program,
 [[:if,
   [:assign,
    [:var_field, [:@ident, "y", [1, 3]]],
    [:vcall, [:@ident, "x", [1, 7]]]],
   [[:assign,
     [:var_field, [:@ident, "z", [1, 10]]],
     [:var_ref, [:@ident, "y", [1, 14]]]]],
   nil]]]

请注意,唯一真正的区别是引发 NameError 的示例使用 :if_mod,而成功的版本使用 :if。显然,后置条件是您所看到的错误、怪癖或错误功能的原因。

怎么办

这种解析行为可能有很好的技术原因,也可能没有。我没有资格评判。但是,如果它对您来说像是一个错误,并且您有动力对此采取一些措施,那么最好的办法是检查Ruby Issue Tracker 以查看它是否已被报告。如果没有,也许是时候有人正式提出来了。

【讨论】:

  • Rubinius 在其后置条件 AST 中使用 :if 代替 :if_mod:require 'pp'; require 'parser/current'; pp Parser::CurrentRuby.parse 'z = y if y = x'
  • 好细节.. 并且干得好,远离争吵。我想我现在终于有了一个链接到问题何时再次出现的问题。
  • 这看起来像是 Rubinius 中的一个错误。
  • @JörgWMittag 为什么?由于首先处理内联if 的条件,因此解析器应该类似地处理它是有意义的。
  • 相关discussion on Twitter 将其解释为 :if_mod 令牌的 LALR 下降问题。
猜你喜欢
  • 2012-03-29
  • 2013-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多