基于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’表示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 ,因此评估为真实。 由于第一个表达式是真实的,下一个表达式由逻辑连接并且也被评估,同样评估为真实。
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
,并且正常if语句(参见下面的AST),那么y就不会真正被定义。 这可能只是后置条件if语句和Ruby处理:if_mod标记的方式的怪癖。
成功:if if而不是:if_mod Tokens
如果您反转逻辑并使用普通的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以查看它是否已被报告。 如果没有,也许是时候有人正式提出来了。
- 如何使RSense自动完成并跳转到rails项目的定义?
- Rails脚手架多元化对于“咖啡馆”不正确
- ActionController :: RoutingError(UserAccountsController的未定义方法`load_and_authorize_resource’:Class):
- 如何将binary32转换为ruby中的float
- 如何在Ruby Gem中包装Ruby C扩展?
- PhoneGap Mobile Rails身份validation(从头开始设计?身份validation?)
- 在rdoc中显示inheritance的方法
- 正则表达式:如果字符串包含空格,则不匹配
- 在rails应用程序中运行rake任务