简述
这实际上与解释器有关。问题出现在MRI Ruby 2.1.2和JRuby 1.7.13中,但在Rubinius中按预期工作。例如,在Rubinius 2.2.10中:
x = true
z = y if y = x
在MRI中,使用
Ripper进行一些探索后发现,即使
AST赋值相似,Ruby在处理后置条件时也会有所不同。实际上,当构建AST时,它会使用不同的标记来表示后置条件,这似乎对赋值表达式的评估顺序产生了影响。这是否应该是这种情况,或者是否可以修复,这是一个需要
Ruby核心团队解答的问题。
为什么逻辑与能够正常工作
x = true
y = x and z = y
这是成功的,因为它实际上是两个连续的赋值操作,因为
x被赋值为
true
并且被解析为真值。由于第一个表达式是真值,连接逻辑
and的下一个表达式也被求值并且同样被解析为真值。
y = x
z = y
换句话说,
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 标记的方式的怪癖。
使用 :if 而不是 :if_mod 标记成功
如果您反转逻辑并使用普通的 if 语句,它可以正常工作:
x = true
if y = x
z = y
end
观察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 是否已经报告了这个问题。如果没有,也许是时候有人正式提出了。
z = 2 if y = x
可以正常工作,而z = w if false
也可以。 - Ry-x=x
不会出现NameError
的小精灵也在这个问题中留下了他们的爪痕。 - roippix=x
不是错误,因为赋值x=
在调用x
之前。 - sawa