Someone's Blog

Ruby の特異クラスを整理


特異クラスとメソッド探索

ここまでは Ruby 初心者でもよく知るところでしょう。

そして Ruby には特異クラスという概念があることも知っています。

class SampleClass
end

object = SampleClass.new
object2 = SampleClass.new

class << object
  def hello
    'hello'
  end
end

object.hello # => 'hello'
object2.hello # undefined method `hello' for an instance of SampleClass (NoMethodError)

object.class.ancestors # => [SampleClass, Object, PP::ObjectMixin, Kernel, BasicObject]

このように、そのインスタンスだけに固有のメソッドを生やすことが可能です。

上記の例では、hello メソッドは、object の特異クラスに所属しています。

ですがここで矛盾に遭遇します。

  1. メソッドはすべてクラスに属する。hello メソッドは、object の特異クラスに属する。
  2. object.class.ancestors が、object が呼び出し可能なメソッドを定義しているクラス群である。
  3. しかし object.class.ancestors に、object の特異クラスは含まれていない。

これはどう考えればいいのでしょうか?

結論からいうと、2は「特異クラス」を考慮に入れていないのです。

2は正確には、object.singleton_class.ancestors が、object が呼び出し可能なメソッドを定義しているクラス群である。 が正しいです。

object.singleton_class.ancestors # => [#<Class:#<SampleClass:0x000000011bf4fcd8>>, SampleClass, Object, PP::ObjectMixin, Kernel, BasicObject]

ancestors の0番目が、object の singleton_class を表しています。

では 「object.class.ancestors が、object が呼び出し可能なメソッドを定義しているクラス群である。」という理解は、正しくなかったのでしょうか?

実はこれは、特異クラスを考慮に入れていないだけで、完全な間違いではないのです。

object.singleton_class.superclass == object.class # => true

上記のように、singleton_class が、class を継承していたのです。

この関係により両者は整合します。

なおすべてのオブジェクトに対して、object.singleton_class.superclass == object.class が成り立つのか、というとそれは間違いです。

class SampleSuperClass
end

class SampleClass < SampleSuperClass
  class << self
    def hello
      "hello"
    end
  end
end

SampleClass.hello # => hello

# どちらも Class になる
SampleClass.class # => Class
SampleClass.singleton_class.superclass.superclass.superclass.superclass # => Class

今度は、

SampleClass.class == SampleClass.singleton_class.superclass.superclass.superclass.superclass # => true

という具合に、singleton_class の superclass をいくつか辿っていくと、class にたどり着きました。

最後にまとめます。

簡単のために、モジュールのミックスインを無視すれば、

  1. (特異クラスを考慮しない場合)オブジェクトのメソッドは、object.class.superclass.superclass ...etc. と superclass を辿って探索する
  2. (特異クラスを考慮する場合)オブジェクトのメソッドは、object.singleton_class.superclass.superclass ...etc と superclass を辿って探索する
  3. 1と2は、object.class == object.singleton_class.superclass.superclass ...etc. の関係が成立するため整合している

と整理することができます。

特異クラスと継承

あるクラスが継承関係にある場合、その特異クラスも継承関係を持ちます。

class SampleSuperClass
  class << self
    def hello
      'hello'
    end
  end
end

class SampleClass < SampleSuperClass
end

SampleClass.superclass == SampleSuperClass # => true
SampleClass.singleton_class.superclass == SampleSuperClass.singleton_class # => true

これがクラスメソッドが継承される仕組みです。

# SampleClass が継承する、SampleSuperClass の クラスメソッド hello が呼び出される
SampleClass.hello # => hello

# SampleClass が呼び出し可能なメソッドは、以下のクラス群に定義されている
# hello は #<Class:SampleSuperClass> ( SampleSuperClass の特異クラス)に所属する
SampleClass.singleton_class.ancestors
# =>
# [#<Class:SampleClass>,
#  #<Class:SampleSuperClass>,
#  #<Class:Object>,
#  #<Class:BasicObject>,
#  Class,
#  Module,
#  Object,
#  PP::ObjectMixin,
#  Kernel,
#  BasicObject]