はじめに
RubyGoldの試験対策をしていく中で、定数探索についてかなり色々調べたのでまとめてみた
基本的な考え方
定数探索の基本的な考え方はレキシカルスコープ→継承ツリー→トップレベル
これだけ覚えておけば大体なんとかなる
基本的なパターン
class A CONST = "A" def const p CONST end end A.new.const #=> "A"
これはCONSTを呼び出してる場所のレキシカルスコープ内にCONSTがあるため、そのままA
が出力される
外部から呼び出すパターン
class A CONST = "A" end class B def const p A::CONST end end B.new.const #=> "A"
レキシカルスコープ、継承以外から呼び出す場合はクラス::定数名
でそのクラスに定義されている定数が呼べる
継承するパターン
class A CONST = "A" end class B < A def const p CONST end end B.new.const #=> "A"
レキシカルスコープに無い && オブジェクトのスーパークラスに無い場合は継承ツリーを辿って定数を探す
この場合はBのインスタンスB.new
のスーパークラスBにCONSTが存在しないため、継承しているAで探す
レキシカルスコープ VS 継承 part1
class A CONST = "A" end class B < A CONST = "B" def const p CONST end end B.new.const #=> "B"
定数はレキシカルスコープ→継承の順番で探索されるため、今回はBが出力される
レキシカルスコープ VS 継承 part2
class A CONST = "A" def const p CONST end end class B < A CONST = "B" end B.new.const #=> "A"
意外にハマりそうな仕様
Aを継承しているためBのインスタンスに対して#constメソッドが呼べるが、 その際に参照されるCONSTはBでは無くconstメソッドを定義しているAのレキシカルスコープが優先される
レキシカルスコープ VS トップレベル
CONST = "Object" class A CONST = "A" def const p CONST end end A.new.const #=> "A"
レキシカルスコープが優先される
継承 VS トップレベル
CONST = "Object" class A CONST = "A" end class B < A def const p CONST end end B.new.const #=> "A"
継承が優先される
特異クラスの定数参照
ここから複雑になる
基本的にレキシカルスコープ→継承の順番で探索することを覚えていれば大丈夫なので、特異クラスの継承が分かっていれば定数探索も分かる
レキシカルスコープ part 1
class A CONST = "A" class << A def const p CONST end end end A.const #=> "A"
CONSTは特異クラスと同じレキシカルスコープで定義されているため参照可能
特異クラスのレキシカルスコープ part 2 (レキシカルスコープ VS レキシカルスコープ)
class A CONST = "class#A" class << self CONST = "singlton#A" def const p CONST end end end A.const #=> "singlton#A"
同一レキシカルスコープ内だと特異メソッド側で定義した値が呼ばれる
特異クラスのレキシカルスコープ part 3 (再オープン)
class A CONST = "class#A" class << self CONST = "singlton#A" end end class << A def const p CONST end end A.const #=> "singlton#A"
特異クラスを再オープンした場合も特異クラスのレキシカルスコープ内に定義される
特異クラスから元クラスの定数は参照できないパターン
class A CONST = "A" end class << A def const p CONST end end A.const #=> uninitialized constant #<Class:A>::CONST
これはエラーになる
探索の流れ↓
CONSTはAの特異クラスのレキシカルスコープ内に無いため継承ツリーを辿ってCONSTを探す
なのでAの特異クラスのスーパークラスはAのスーパークラスの特異クラスになるためA.superclass => Object
の特異クラスに探しに行く
以下の例だと定数が参照できる
class A end CONST = "class#object" # => ObjectにCONSTを定義 class << Object CONST = "singlton#object" # => Objectの特異クラスにCONSTを定義 end class << A def const p CONST end end A.const #=> "singlton#object"
定数探索の順番は
Aの特異クラスのレキシカルスコープ→Aのスーパークラス(Object)の特異クラス
今回はObjectの特異クラス class << Object
にあるCONSTが呼び出された
特異クラスのスーパークラスはクラスのスーパークラスの特異クラス
この呪文を覚えていれば大丈夫
まとめ
定数探索とメソッド探索で若干違うので、勘違いしないように注意します