irbで調べるObject, Module, Classの関係

RubyのObject, Module, Classの関係と特異メソッドとインスタンスメソッドの話でメンバーを混乱に陥れたので補足しておきます。

継承関係

まずは、irbでクラスの継承関係を調べてみましょう。

Class.superclass
#=> Module
Module.superclass
#=> Object
Object.superclass
#=> nil

ということなので、

Class < Module < Object

という継承関係が成り立っていることが確認できます。ここだけ見ると、「ClassはModuleの派生クラスである」と言えます。実際、

Class < Module
#=> true

ですし。

インスタンスの生成関係

Rubyではすべてがオブジェクト(=インスタンス)ですので、ObjectとかClassもインスタンスとして扱えるはずです。こんどはClass, Module, Objectのclassを見てみましょう。

Class.class
#=> Class
Module.class
#=> Class
Object.class
#=> Class

まあ当然と言えば当然。Class, Module, Objectはそれぞれインスタンスを生成できるので、Classのインスタンスです。ClassもClassのインスタンスなの?と考えちゃうとどっちが先に生まれたの?的なループに陥りそうですが、この3つのオブジェクトはそういうものだ!と考えちゃった方が楽かもしれません。

納得できなかったらまずClassというオブジェクトがあって、そのClass.classメソッドは自分自身を返すメソッドだと自分をごまかしてください。それでも納得できなかったらRHGを読んでください*1http://i.loveruby.net/ja/rhg/book/class.html

モジュールとの関係

今度はModule#ancestorsメソッドを使って関係するモジュール(クラスを含む)を調べましょう。Module#ancestorsメソッドは、自分自身とincludeしているモジュールと、自身がクラスなら継承しているクラスすべてを返します。

Class.ancestors
#=> [Class, Module, Object, Kernel]
Module.ancestors
#=> [Module, Object, Kernel]
Object.ancestors
#=> [Object, Kernel]
Kernel.ancestors
#=> [Kernel]

Kernelというのが出てきました。説明は http://www.ruby-lang.org/ja/man/html/Kernel.html に任せるとして

Kernel.class
#=> Module
Kernel.respond_to?(:superclass)
#=> false

Kernel.classがModuleを返しているところに注目してください。Class, Module, Objectは、すべてClassのインスタンスでしたが、KernelはModuleのインスタンスです。

まとめ

整理しながら図にしてみると、まず継承ツリーは

Object <|---- Module <|---- Class

で、これらはすべてのClassのインスタンスなので、

Object:Class <|---- Module:Class <|---- Class:Class

となり、ObjectはModuleのインスタンスであるKernelをincludeしているので

Kernel:Module <- - - Object:Class <|---- Module:Class <|---- Class:Class

という風な感じになります。

これら4つのオブジェクトは最初っからRubyがそういうものとして用意しているので、そういうもんだって飲み込んじゃった方が楽な気もします。


特異メソッド再び

http://d.hatena.ne.jp/akm/20100218#1266514416 でも特異メソッドの話を書きましたが、もう一回。

特異メソッドは、普通に定義してないメソッド、と言い換えることができますが、では普通に定義したメソッド、というとなんなのか?が問題になります。

で、ここで理屈をこねこねするよりも、調べる方法を紹介してあとは自分で試してもらった方がいいのかな?という気がしてきたので、先にそちらを紹介します。

どうするかと言うと、あるオブジェクトに対してsingleton_methodsに含まれているものが特異メソッドです。例えば、

obj = Object.new
#=> #<Object:0x100305998>
def obj.foo
  "foo"
end
#=> nil
class << obj
  def bar
    "bar"
  end
end
#=> nil
obj.instance_eval do
  def baz
    "baz"
  end
end
#=> nil

というobjというオブジェクトがあったとして、singleton_methodsを呼び出すと、ちゃんと特異メソッドを教えてくれます。

obj.singleton_methods
#=> ["foo", "baz", "bar"]

で、たとえばClassのインスタンスである何らかのクラスの特異メソッドについては、インスタンスを作らなくてもクラスに対して直接呼び出せるメソッドなので、「クラスメソッド」という呼び方が付いています。

Time.singleton_methods
#=> ["times", "at", "gm", "utc", "mktime", "_load", "now", "local"]

extendと特異メソッド

リファレンスの言葉がすべてです。

引数で指定したモジュールのインスタンスメソッドを self の特異 メソッドとして追加します。

例を挙げると

module B
  def foo
    "FOOOO"
  end
end
#=> nil
B.instance_methods
#=> ["foo"]

i = Object.new
i.extend(B)
i.singleton_methods
#=> ["foo"]

という風になります。着目すべきは、Bのインスタンスメソッドfooが、iの特異メソッドとして追加されている、という点です。

ちなみに、singleton_methodsで特異メソッドを調べることはできますが、extendしたモジュールを調べることはできません。これはあくまで、extendが特異メソッドを追加するメソッドであり、オブジェクトにextendしたことを記録しないためかと思います。

例えば、

class A
  include B
end
#=> A
A.ancestors
#=> [A, B, Object, Kernel]

A2 = A.dup
#=> A2
A2.ancestors
#=> [A2, B, Object, Kernel]

という風にAをコピーして新たなクラスA2を生成したときには、Aがincludeした情報はA2にも伝わりますが、

i = Object.new
i.extend(B)
i.singleton_methods
#=> ["foo"]

i2 = i.dup
i2.singleton_methods
#=> []

Bをextendしたiからのコピーi2には、fooという特異メソッドはコピーされません。特異メソッドは特定のオブジェクトにのみで使えるメソッド、ということのようです。

instance_evalとmodule_eval(class_eval)

Object#instance_eval

オブジェクトのコンテキストで文字列 expr を評価してその結果を 返します。
(中略)
オブジェクトのコンテキストで評価するとは self をそのオブジェ クトにして実行するということです。また、文字列/ブロック中でメソッ ドを定義すれば self の特異メソッドが定義されます。

Module#module_eval

モジュールのコンテキストで文字列 expr を評価してその結果を返 します。
(中略)
モジュールのコンテキストで評価するとは、実行中そのモジュールが self になるということです。つまり、そのモジュールの定義文の 中にあるかのように実行されます。

分かりやすい説明はObject#instance_evalの方かと思います。

instance_eval内で定義したメソッドは特異メソッドになり、module_eval内で定義したメソッドはそのモジュール(クラス含む)のインスタンスメソッド(=普通のメソッド)になる訳です。

インスタンスメソッドを定義するためには、かならずモジュール(クラス含む)が必要なので、module_evalはModuleのインスタンスメソッドになっているって寸法ですね。

*1:僕は2度挫折してます