未定义的本地变量基于Ruby中的语法
在以下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}"
它导致
未定义的局部变量或方法'y'for main:Object(NameError)
这是为什么?
我明白我正在做一个任务,并隐式检查赋值,以确定是否运行z = y
。 我也明白,如果我在x = 5
行之后添加y, y = nil
声明,它将按预期方式通过并运行。
但是,期望语言应该首先评估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核心团队来说,这是否应该是这种情况,还是可以修复的问题。
为什么它与逻辑和
x = true
y = x and z = y
这是成功的,因为它实际上是按顺序分配的两个分配,因为true
分配给了x,因此评估为truthy。 由于第一个表达式是truthy,下一个由逻辑连接的表达式也被评估,并且同样评估为truthy。
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
,那么y就不会是未定义的,就像它与正常的if语句一样(请参阅下面的AST)。 这可能只是后置条件的一个怪癖,如果语句和Ruby处理if_mod标记的方式。
成功与:if而不是:if_mod令牌
如果你反转逻辑并使用正常的if语句,它可以正常工作:
x = true
if y = x
z = y
end
#=> true
看着开膛手产生下面的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。 看起来好像后条件是你看到的错误,怪癖或错误的原因。
该怎么做
这种解析行为可能有一个很好的技术原因,或者可能没有。 我没有资格评判。 但是,如果它看起来像是一个bug,并且您有动力去做某件事情,那么最好的办法是检查Ruby Issue Tracker以查看它是否已被报告。 如果没有,也许是有人正式提出来的。
链接地址: http://www.djcxy.com/p/81481.html