日々徒然

プログラミングしたりお酒飲んだりする毎日

(完全に理解する)Rubyの定数探索

はじめに

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が呼び出された

特異クラスのスーパークラスはクラスのスーパークラスの特異クラス

この呪文を覚えていれば大丈夫

まとめ

定数探索とメソッド探索で若干違うので、勘違いしないように注意します