即使person
不存在,我也惊讶地发现params[:person_id]
是由以下代码行定义的:
person = Person.find(params[:person_id]) if params[:person_id]
我有点期望Ruby首先检查if
语句,然后才定义person
。实际上,似乎person
的定义早于nil
,但仍然是irb> foo
# NameError (undefined local variable or method `foo' for main:Object)
irb> if false
irb> foo = 'bar'
irb> end
irb> foo
# => nil
。
在调查中,我尝试了以下操作:
foo
最初if
是未定义的。但是,即使仅在未评估的foo
块中引用了它,也对其进行了定义。
我现在猜测整个程序都已解析(?),并且nil
节点已添加到抽象语法树(即已定义)中。然后执行程序(?),但是跳过了该行(未求值(?)),因此foo为public float[] rectangleToVerts(float x, float y, float width, float height)
float[] verts = new float[8];
verts[0] = x - width / 2;
verts[1] = y + height / 2;
verts[2] = x + width / 2;
verts[3] = y + height / 2;
verts[4] = x + width / 2;
verts[5] = y - height / 2;
verts[6] = x - width / 2;
verts[7] = y - height / 2;
return verts;
}
public void tick(){
shape.setVertices(rectangleToVerts(x, y, 5, 5);
}
(已定义但未设置为值)。
我不确定如何确认或驳斥这种预感。如何学习和挖掘Ruby的内部原理,并发现在这种特定情况下会发生什么?
答案 0 :(得分:0)
回答我自己的问题,将Jay的answer to a similar question链接到section of the docs并在其中进行解释:
解析器遇到分配时创建本地变量,而不是分配发生时创建
Ruby Hacking Guide中对此有更深入的分析(没有可用的部分链接,搜索或滚动到“局部变量定义”部分):
顺便说一句,它是在“它出现”时定义的,这意味着即使未分配它也已定义。定义的[但尚未分配]变量的初始值为nil。
这回答了最初的问题,但没有回答如何了解更多信息。
Jay和simonwo都由Pat Shaughnessy推荐Ruby Under a Microscope,我很想读。
此外,《 Ruby Hacking指南》的其余部分涵盖了很多细节,并实际检查了基础C代码。 Objects和Parser两章与最初有关变量分配的问题特别相关(与Variables and constants一章无关,它只是使您回到Objects一章)。
我还发现Parser gem是查看解析器如何工作的有用工具。安装(gem install parser
后,您可以开始检查不同的代码位,以查看解析器对它们的作用。
该gem还捆绑了ruby-parse
实用程序,该实用程序使您可以检查Ruby解析不同代码段的方式。 -E
和-L
选项对我们来说最有趣,如果我们只想处理Ruby片段,例如-e
,则foo = 'bar'
选项是必需的。例如:
> ruby-parse -E -e "foo = 'bar'"
foo = 'bar'
^~~ tIDENTIFIER "foo" expr_cmdarg [0 <= cond] [0 <= cmdarg]
foo = 'bar'
^ tEQL "=" expr_beg [0 <= cond] [0 <= cmdarg]
foo = 'bar'
^~~~~ tSTRING "bar" expr_end [0 <= cond] [0 <= cmdarg]
foo = 'bar'
^ false "$eof" expr_end [0 <= cond] [0 <= cmdarg]
(lvasgn :foo
(str "bar"))
ruby-parse -L -e "foo = 'bar'"
s(:lvasgn, :foo,
s(:str, "bar"))
foo = 'bar'
~~~ name
~ operator
~~~~~~~~~~~ expression
s(:str, "bar")
foo = 'bar'
~ end
~ begin
~~~~~ expression
链接到顶部的两个参考都突出显示了一个边缘情况。 Ruby docs使用示例p a if a = 0.zero?
,而Ruby Hacking Guide使用等效示例p(lvar) if lvar = true
,两者都引发NameError
。
旁注::请记住,=
表示分配,==
表示比较。边缘情况下的if foo = true
构造告诉Ruby检查表达式foo = true
的计算结果是否为true。换句话说,它将值true
分配给foo
,然后检查分配结果是否为true
(将是)。这很容易与更常见的if foo == true
混淆,后者仅检查foo
与true
的比较是否相等。由于两者很容易混淆,因此如果我们在条件warning: found `= literal' in conditional, should be ==
中使用赋值运算符,Ruby将发出警告。
使用ruby-parse
实用程序,将原始示例foo = 'bar' if false
与边缘情况foo if foo = true
进行比较:
> ruby-parse -L -e "foo = 'bar' if false"
s(:if,
s(:false),
s(:lvasgn, :foo,
s(:str, "bar")), nil)
foo = 'bar' if false
~~ keyword
~~~~~~~~~~~~~~~~~~~~ expression
s(:false)
foo = 'bar' if false
~~~~~ expression
s(:lvasgn, :foo,
s(:str, "bar"))
foo = 'bar' if false # Line 13
~~~ name # <-- `foo` is a name
~ operator
~~~~~~~~~~~ expression
s(:str, "bar")
foo = 'bar' if false
~ end
~ begin
~~~~~ expression
正如您在上面的输出的第13和14行上看到的那样,在原始示例中,foo是一个名称(即变量)。
> ruby-parse -L -e "foo if foo = true"
s(:if,
s(:lvasgn, :foo,
s(:true)),
s(:send, nil, :foo), nil)
foo if foo = true
~~ keyword
~~~~~~~~~~~~~~~~~ expression
s(:lvasgn, :foo,
s(:true))
foo if foo = true # Line 10
~~~ name # <-- `foo` is a name
~ operator
~~~~~~~~~~ expression
s(:true)
foo if foo = true
~~~~ expression
s(:send, nil, :foo)
foo if foo = true # Line 18
~~~ selector # <-- `foo` is a selector
~~~ expression
在边缘示例中,第二个foo也是变量(第10和11行),但是当我们看第18和19行时,我们看到第一个foo被标识为选择器(即方法)。
这表明解析器决定事物是方法还是变量,并以与稍后进行评估的顺序不同的顺序解析行。
考虑边缘情况...
解析器运行时:
if
关键字分隔的表达式foo
以小写字母开头,因此它必须是方法或变量。它不是现有变量,并且没有赋值运算符,因此解析器认为它必须是方法foo = true
被分解为表达式,运算符,表达式。同样,表达式foo
也以小写字母开头,因此它必须是方法或变量。它不是现有变量,但是后面是赋值运算符,因此解析器知道将其添加到局部变量列表中。稍后评估者运行时:
true
分配给foo
foo
方法(除非我们使用NameError
处理,否则它将引发method_missing
)。