在以下Ruby代码中,
#! /usr/bin/env ruby
x = true
y = x and z = y
puts "z: #{z}"
Run Code Online (Sandbox Code Playgroud)
它将z: true按预期输出.
但在下面的一个中,我希望它具有相同的行为:
#! /usr/bin/env ruby
x = true
z = y if y = x
puts "z: #{z}"
Run Code Online (Sandbox Code Playgroud)
它导致了
未定义的局部变量或方法'y'表示main:Object(NameError)
这是为什么?
我理解我正在做一个赋值,并隐式检查赋值以确定是否运行z = y.我也明白,如果我添加y的声明y = nil,就x = 5在行之后,它将按预期传递并运行.
但是,期望语言if首先评估部分然后评估其内容,并且第二块代码与第一块代码的行为相同,这是不正确的?
这实际上是特定于解释器的。该问题出现在 MRI Ruby 2.1.2 和 JRuby 1.7.13 中,但在Rubinius中按预期工作。例如,对于 Rubinius 2.2.10:
x = true
z = y if y = x
#=> true
Run Code Online (Sandbox Code Playgroud)
在 MRI 中,使用Ripper进行的一些探索表明,即使AST分配相似,Ruby 也会以不同的方式处理后置条件。它实际上在构建 AST 时对后置条件使用不同的标记,这似乎对赋值表达式的求值顺序有影响。是否应该出现这种情况,或者是否可以修复,这是Ruby 核心团队的问题。
x = true
y = x and z = y
Run Code Online (Sandbox Code Playgroud)
这是成功的,因为它实际上是按顺序进行的两个赋值,因为true被分配给x,因此计算结果为真。由于第一个表达式为真,因此由逻辑and连接的下一个表达式也被求值,并且同样被求值为真。
y = x
#=> true
z = y
#=> true
Run Code Online (Sandbox Code Playgroud)
换句话说,x被赋予值true,然后z也被赋予值true。任一赋值的右侧都不会未定义。
x = true
z = y if y = x
Run Code Online (Sandbox Code Playgroud)
在这种情况下,实际上首先评估后置条件。您可以通过查看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]]]]]]]
Run Code Online (Sandbox Code Playgroud)
与您的第一个示例不同,其中y在第一个表达式中分配true,因此true在分配给z之前在第二个表达式中解析为,在这种情况下y在仍未定义时进行评估。这会引发NameError。
当然,人们可以合理地认为这两个表达式都包含赋值,并且如果 Ruby 的解析器像使用普通 if 语句一样首先进行计算,那么y就不会真正是未定义的(请参见下面的 AST)。y = x这可能只是后置条件 if 语句的一个怪癖以及 Ruby 处理 :if_mod 标记的方式。
如果你反转逻辑并使用普通的 if 语句,它就可以正常工作:
x = true
if y = x
z = y
end
#=> true
Run Code Online (Sandbox Code Playgroud)
查看 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]]]
Run Code Online (Sandbox Code Playgroud)
请注意,唯一真正的区别是引发 NameError 的示例使用 :if_mod,而成功的版本使用 :if。看来后置条件确实是您所看到的错误、怪癖或故障的原因。
这种解析行为可能有很好的技术原因,也可能没有。我没有资格评判。但是,如果您认为它是一个错误,并且您有动力对此采取措施,那么最好的办法是检查Ruby 问题跟踪器,看看它是否已被报告。如果没有,也许是时候有人正式提出这个问题了。