【一人 Ruby Advent Calendar 2017】トップレベルの変数の扱い【19日目】
一人 Ruby Advent Calendar 2017 19日目の記事になります。
前回、トップレベルのメソッドの扱いについて書いたのですが、今回は変数に関してです。
トップレベルのローカル変数
トップレベルで定義されたローカル変数は通常のローカル変数と同じような感じで使えます。
homu = "homu" def func homu end # 関数内からは参照できない # Error: undefined local variable or method `homu' for main:Object (NameError) # p func # 関数内で参照する場合はブロックを使ってキャプチャする define_method(:func){ homu } p func # => "homu"
また、ローカル変数の寿命は『プログラム終了』までで、他のファイルからは参照できません。
トップレベルのインスタンス変数
さて、ややこしいのはトップレベルで定義されているインスタンス変数です。
トップレベルで定義されたインスタンス変数は self
、つまり main
オブジェクトのインスタンス変数として定義されます。
@homu = "homu" p self.instance_variables # => [:@homu] p self.instance_variable_get :homu # => "homu"
また、トップレベルのメソッド内等からも参照できるように『見えます』。
@homu = "homu" def homu @homu end p homu # => "homu"
これがちょっと勘違いするポイントなんですが、
わけではなくて
- トップレベルで呼び出したメソッドのコンテキストが
main
オブジェクトになる
のでメソッド内で参照しているインスタンス変数が self
、つまり main
オブジェクトから参照する事になります。
まあ文章で説明してもわかりづらいと思うので実際にコードを書いてみましょう。
def homu self end # トップレベルで呼び出した場合、homu メソッドのコンテキストは main になる p homu # => main class X def func homu end end # 別のオブジェクトのメソッド内で呼び出した場合は、そのオブジェクトのコンテキストになる p X.new.func # => #<X:0x00000000008796a0>
このようにトップレベルで定義されたメソッドは『呼びだされたレシーバのコンテキスト』として呼ばれます。
なので、トップレベルのメソッド内でトップレベルのインスタンス変数を参照しようとすると呼び出された場所によって結果が異なります。
@homu = "homu" def homu @homu end # トップレベルで呼びだされた場合、コンテキストは main になるので # main のインスタンス変数、つまりトップレベルの @homu が参照される p homu # => "homu" class X def func homu end end # X のインスタンスメソッドから呼び出した場合は、コンテキストが X オブジェクトになるので # X オブジェクトの @homu を呼びだそうとする # X オブジェクトのインスタンス変数 @homu は未定義なので nil を返す p X.new.func # => nil
うーん、ややこしいですね…。
まとめ
- トップレベルで定義したローカル変数はそのファイルのローカル変数として定義される
- トップレベルで定義したインスタンス変数は
main
オブジェクトのインスタンス変数として定義される - トップレベルで定義されたメソッドのコンテキストはレシーバによって異なるので注意する
この挙動は Ruby の
self
がどのように参照されているのかObject
で定義されたメソッドはどのように扱われるのか
などを理解していればとても理にかなっている挙動にはなるんですが、そのあたりがわかっていないとかなり難しいところではありますね…。