[Ruby][DONE] ローカル変数が生成されるタイミング
疑問: なぜ "ruby -e 'p(v=v)'" が NameError にならないのか?
Rubyのレキシカルスコープのすごさを同僚に説明している中で気づいた。
% ruby -e 'p v' -e:1: undefined local variable or method `v' for main:Object (NameError) % ruby -e 'p (v=v)' nil
なぜ、未定義変数を自分自身に代入するときには、NameErrorにならないのだろう。
答えは、Rubyのパーサが「代入文を見つけた時点で変数が定義される」仕様になっているため。
以下、詳細。
定数の場合は NameError になる
% ruby -e 'p(FOO=FOO)' -e:1: uninitialized constant FOO (NameError)
変数もこの挙動を期待してたのだが。
Rubyの変数仕様
以下の記述を見つけた。
宣言は、例え実行されなくても宣言とみなされます。
v = 1 if false # 代入は行われないが宣言は有効 p defined?(v) # => "local-variable" p v # => nilhttp://doc.ruby-lang.org/ja/1.9.3/doc/spec=2fvariables.html
これは、「パーサが代入文を見つけたときに、変数定義をしてしまう」ということだろうか。
RHG に書いてあった
RHG (Ruby Hacking Guide, もしくは『Rubyソースコード完全解説』)に答えが書いてあった。
(2ヶ月前に読んだばかりなのに、思い出せなかった…)
「12.4.1 ローカル変数の定義」
(中略)
ところで、定義されるのは代入が「現れたとき」なので、実際には代入が行われなくても定義されるということになる。定義された変数の初期値はnilだ。if false lvar = "この代入は決して実行されない" end p lvar # nil と表示されるまたさらに、定義されるのは代入が「現れた」「とき」なので記号列上で参照の前になくてはいけない。たとえば次のような場合は定義されていない。
p lvar # 定義されていない! lvar = nil # ここで現れているのだが…「記号列上で」というところに注意してほしい。評価順とはまるで関係がない。たとえば次のようなコードなら当然条件式を先に評価するが、記号列順だとpの時点ではlvarへの代入は現れていない。したがって NameError になる。
p(lvar) if lvar = true
なるほど、つまり、プログラム中で代入文が「現れた」タイミングで変数は定義され、そのときの値はnilになる。
その後、実際の代入処理が「評価」されたタイミングで、本当の値が代入される、ということか。
動作を確認
上記を確かめてみる。
if false; vv=true; end; p vv # => nil p (vv) if vv=true # => NameError vv=vv; p vv # => nil
つまり、最後の例では、
という順番で、 "vv=vv" の評価結果が nil になるのか。
Rubyソースを見る
"vv=vv" を評価するときは、 parse.y の以下のルールが使われてるようだ。
lhs : user_variable { $$ = assignable($1, 0); /*%%%*/ if (!$$) $$ = NEW_BEGIN(0); /*% $$ = dispatch1(var_field, $$); %*/ } arg : lhs '=' arg { /*%%%*/ value_expr($3); $$ = node_assign($1, $3); /*% $$ = dispatch2(assign, $1, $3); %*/ }
C の中では、以下の呼び出し連鎖:
assignable() local_var_gen() vtable_add()
つまり、パーサが変数代入を見つけたタイミング(lhsへのREDUCE時)に変数が設定(値はnil)されて、その後に右辺を評価する、という動作。
今までの結果とも合致するので、この考え方でよいみたい。