在以下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}"

结果是



这是为什么?

我了解我正在执行分配,并隐式检查分配值以确定是否运行z = y。我还理解,如果我在y = nil行之后添加y声明x = 5,它将通过并按预期运行。

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

最佳答案

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的解析器首先像对常规if语句那样评估y = x,则y不会真正未定义(请参见下面的AST)。这可能只是后置条件if语句的一个怪癖,以及Ruby处理:if_mod token 的方式。

成功:if代替:if_mod token

如果您颠倒逻辑并使用普通的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来查看它是否已经被报告过。如果没有,也许是时候有人正式提出来了。

10-07 19:07
查看更多