Rubyの定数参照について頭を整理した
最近は、空いた時間にRuby認定試験(Gold)の対策をやっているのですが、 本を読んでいてわかった気になっていた箇所がまぁ、多いこと。。。
練習問題をやってみると、そのへんがはっきりをわかるので良いです。
今回はかなり基本的なことなのですが、定数参照について頭を整理するためにまとめていこうと思います。
Rubyの定数を参照する順番
レキシカルスコープの探索 -> クラス探索 の順番で探索します。
例えば、以下の場合は、CONST_A
が表示されます。
class A NAME = "CONST_A" def name NAME end end class B < A NAME = "CONST_B" end puts B.new.name #=> CONST_A
BクラスにAのメソッド def name ~ end
を追加すると、CONST_B
が表示されます。
また、NAME = "CONST_B"
を消すと、継承チェーンをたどって、CONST_A
が表示されるようになります。
ネストしている場合
もちろんですがネストしている場合、階層が異なるものはそれぞれ別の値が保持されているので、そのスコープに合わせて参照されます。
module M class A CONST = "M::A" def say CONST end end end module M module B class A CONST = "M::B::A" def say CONST end end end end ma = M::A.new puts ma.say # => M::A mba = M::B::A.new puts mba.say # => M::B::A
では、この場合はどうなるかというと
module M CONST = "Hello" end module M class C def say CONST end end end puts M::C.new.say #=> Hello
moduleを再オープンしている場合でも、値は保持されているので Hello が表示されます。
以下のように M::C
と記述するとクラスMの探索は行われないようです。
module M CONST = "Hello" end class M::C def say CONST end end puts M::C.new.say #=> uninitialized constant M::C::CONST (NameError)
Cクラスにいる定数は参照可
module M class C CONST = "Hello" end end class M::C def say CONST end end puts M::C.new.say #=> Hello
includeなどが入ったとき
続いて、includeやprependなどが入ったとき。 こちらも同じく、継承チェーンのどこにクラスが入ってくるかわかれば問題なさそうです。
class B CONST = "Hello B" end module C CONST = "Hello C" end module D CONST = "Hello D" end class A < B include C include D def say CONST end end a = A.new p a.say # => Hello D
呼び出したところから、一番近い Hello D
が参照されます。
A.ancestors => [A, D, C, B, Object, Kernel, BasicObject]
まとめ
定数の参照については、
- 呼び出している箇所はどこか?
- 定数はどのクラス・モジュールにいるのか?
- レキシカルスコープはどうなっているか?
- 継承チェーンはどうなっているか?
を、モジュールのネストも意識しつつ確認していけばよさそうです。
その他
こちらは何が出力されるか?というと、
class A CONST = "Hello A" class << self def name const_get(:CONST) end end end class B < A CONST = "Hello B" end puts B.name #=> Hello B
const_get
は、selfに定義された定数を探索するので、Bクラスの持っているCONST Hello B
が表示されます。