我无法向自己解释的简单代码:
puts a if a = 1
这导致
warning: found = in conditional, should be ==
NameError: undefined local variable or method 'a' for main:Object
虽然现在检查a
后我们可以看到它已被定义:
a #=> 1
为什么a
被分配给1
,尽管引发了异常?
来自docs:
混淆来自无序执行表达式。 首先分配局部变量,然后尝试调用a 不存在的方法[
a
]。
这部分仍然令人困惑 - 为什么解释器没有检测到已定义的局部变量a
,并且仍然试图调用"不存在"方法?如果它也不检查局部变量,找到定义的局部变量a
并打印1
?
答案 0 :(得分:5)
让我们看看Ruby的修饰符if
的抽象语法树:
$ ruby --dump=parsetree -e 'puts a if a = 1'
# @ NODE_SCOPE (line: 1, code_range: (1,0)-(1,15))
# +- nd_tbl: :a
# +- nd_args:
# | (null node)
# +- nd_body:
# @ NODE_PRELUDE (line: 1, code_range: (1,0)-(1,15))
# +- nd_head:
# | (null node)
# +- nd_body:
# | @ NODE_IF (line: 1, code_range: (1,0)-(1,15))
# | +- nd_cond:
# | | @ NODE_DASGN_CURR (line: 1, code_range: (1,10)-(1,15))
# | | +- nd_vid: :a
# | | +- nd_value:
# | | @ NODE_LIT (line: 1, code_range: (1,14)-(1,15))
# | | +- nd_lit: 1
# | +- nd_body:
# | | @ NODE_FCALL (line: 1, code_range: (1,0)-(1,6))
# | | +- nd_mid: :puts
# | | +- nd_args:
# | | @ NODE_ARRAY (line: 1, code_range: (1,5)-(1,6))
# | | +- nd_alen: 1
# | | +- nd_head:
# | | | @ NODE_VCALL (line: 1, code_range: (1,5)-(1,6))
# | | | +- nd_mid: :a
# | | +- nd_next:
# | | (null node)
# | +- nd_else:
# | (null node)
# +- nd_compile_option:
# +- coverage_enabled: false
标准if
:
$ ruby --dump=parsetree -e 'if a = 1 then puts a end'
# @ NODE_SCOPE (line: 1, code_range: (1,0)-(1,24))
# +- nd_tbl: :a
# +- nd_args:
# | (null node)
# +- nd_body:
# @ NODE_PRELUDE (line: 1, code_range: (1,0)-(1,24))
# +- nd_head:
# | (null node)
# +- nd_body:
# | @ NODE_IF (line: 1, code_range: (1,0)-(1,24))
# | +- nd_cond:
# | | @ NODE_DASGN_CURR (line: 1, code_range: (1,3)-(1,8))
# | | +- nd_vid: :a
# | | +- nd_value:
# | | @ NODE_LIT (line: 1, code_range: (1,7)-(1,8))
# | | +- nd_lit: 1
# | +- nd_body:
# | | @ NODE_FCALL (line: 1, code_range: (1,14)-(1,20))
# | | +- nd_mid: :puts
# | | +- nd_args:
# | | @ NODE_ARRAY (line: 1, code_range: (1,19)-(1,20))
# | | +- nd_alen: 1
# | | +- nd_head:
# | | | @ NODE_DVAR (line: 1, code_range: (1,19)-(1,20))
# | | | +- nd_vid: :a
# | | +- nd_next:
# | | (null node)
# | +- nd_else:
# | (null node)
# +- nd_compile_option:
# +- coverage_enabled: false
唯一的区别是puts
的方法参数:
# | | | @ NODE_VCALL (line: 1, code_range: (1,5)-(1,6))
# | | | +- nd_mid: :a
VS
# | | | @ NODE_DVAR (line: 1, code_range: (1,19)-(1,20))
# | | | +- nd_vid: :a
使用修饰符if
,解析器将a
视为方法调用并创建NODE_VCALL
。这指示解释器进行方法调用(尽管 是局部变量a
),从而产生NameError
。 (因为没有方法a
)
使用标准if
,解析器将a
视为局部变量并创建NODE_DVAR
。这指示解释器查找一个按预期工作的局部变量。
如您所见,Ruby在解析器级别识别局部变量。这就是文档说的原因:(强调添加)
由于解析顺序,修饰符和标准版本[...]不是彼此的精确转换。
答案 1 :(得分:2)
Ruby从左到右解析代码。在解析对它们的第一次赋值时,定义局部变量。在puts a
,尚未解析a
的赋值,因此局部变量a
尚不存在,Ruby假定a
是方法调用。局部变量仅存在于右和以下分配。
在运行时,Ruby必须评估条件以确定是否执行puts
,因此a
被初始化为1
。
您似乎在某种REPL中执行该代码。通常,REPL抢救异常而不是终止,这就是为什么你的代码继续执行而不是终止,并且因为我们现在低于赋值,所以定义了变量,并且自从执行赋值以来,变量被初始化。
如果您不清楚变量的定义和初始化之间的区别,请冥想:
foo
# NameError
if false
foo = 42
end
foo
#=> nil
foo = :bar
foo
#=> :bar