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 が表示されます。

参考