Techioz Blog

ローカル変数としての解釈はメソッド名をオーバーライドしますか?

概要

この質問のように、定義されていないローカル変数が独自の代入内で使用されると、nil と評価されます。

x = x # => nil 

ただし、ローカル変数の名前が既存のメソッド名と競合する場合は、さらに注意が必要になります。以下の最後の例が nil を返すのはなぜですか?

{}.instance_eval{a = keys} # => []
{}.instance_eval{keys = self.keys} # => []
{}.instance_eval{keys = keys} # => nil

解決策

Ruby では、明示的なレシーバーや括弧なしでメソッドを呼び出すことができるため、ローカル変数参照とレシーバーなしの引数なしのメソッド呼び出しの間には構文上のあいまいさが存在します。

foo

「引数なしで自分自身に対してメソッド foo を呼び出す」または「ローカル変数 foo を逆参照する」のいずれかを意味します。

スコープ内にローカル変数 foo が存在する場合、これはメソッド呼び出しとしてではなく、常にローカル変数の逆参照として解釈されます。

ローカル変数は解析時に定義されます。ローカル変数への代入がパーサーによって認識されると、その時点からローカル変数がスコープ内になります。ただし、これは実行時にのみ初期化され、コードのコンパイル時の評価は行われません。では、ローカル変数が「スコープ内にある」とは何を意味するのでしょうか?これは、実行時の意味論ではなく、解析時に構文的に決定されます。これはとても重要です!

if false
  foo = 42 # from this point on, the local variable foo is in scope
end

foo # evaluates to nil, since it is declared but not initialized

ローカル変数がメソッドを回避するのではなく、メソッドを「シャドウ」するのが理にかなっているのはなぜですか?そうですね、メソッドがローカル変数をシャドウする場合、それらのローカル変数を逆参照する方法はなくなります。ただし、ローカル変数がメソッドをシャドウする場合でも、それらのメソッドを呼び出す方法はまだあります。あいまいさはレシーバーなしの引数なしメソッド呼び出しにのみ存在することを覚えておいてください。明示的なレシーバーまたは明示的な引数リストを追加した場合でも、メソッドを呼び出すことができます。

def bar; 'Hello from method' end; public :bar

bar # => 'Hello from method'

bar = 'You will never see this' if false

bar # => nil

bar = 'Hello from local variable'

bar      # => 'Hello from local variable'
bar()    # => 'Hello from method'
self.bar # => 'Hello from method'