#! /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}"
它导致了
undefined local variable or method ‘y’ for main:Object (NameError)
这是为什么?
我知道我正在做一个赋值,并隐式检查赋值以确定是否运行z = y.我也明白,如果我在x = 5行之后添加y,y = nil的声明,它将按预期传递并运行.
解决方法
这实际上是特定于解释器的.问题出现在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,"x",13]]]],"z",0]]],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,3]]],7]]]],[[:assign,10]]],[:var_ref,14]]]]],nil]]]
请注意,唯一真正的区别是引发NameError的示例使用:if_mod,而成功使用的版本使用:if.当然,后期条件是您所看到的错误,怪癖或错误的原因.
怎么办呢
这种解析行为可能有很好的技术原因,或者可能没有.我没资格判断.但是,如果它看起来像你的错误,并且你有动力去做一些事情,那么最好的办法就是检查Ruby Issue Tracker以查看它是否已被报告.如果没有,也许是时候有人正式提出来了.