在以下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来查看它是否已经被报告过。如果没有,也许是时候有人正式提出来了。