[ruby-core:18437] Class as second-generation singleton class を読んだ当初は、特異クラスのクラスが Class ってことでええやん!と思ったのですが、改めて調べてみるとどうやら必ずしもすべての特異クラスが Class に属するわけではないようで(かつ、確認する過程で、id:sumim:20080111:p1 の間違いを見つけてしまったり、id:sumim:20061019:p1 の謎が解けたりもしたので)、この機会に表題の件についていったん図にして自分なりに理解を整理しておくことに。
関連:
Ruby で、クラスのメタ階層の情報を得る際の注意として、Ruby の #ancestors は隠しごとをするし(特異クラスは通常は不可視)、#class と #superclass はレシーバが特異クラスであっても正確な情報を返さないので、これらはほとんどあてにならないということを知っておく必要があります(#superclass に関しては、1.9 では建前と実際が一致するようになったみたいです)。あと、class << self; self end のイディオムは特異クラスがないときに作ってそれを返してくる副作用(というか本来の機能)があることも忘れてはいけないでしょう。
こうした制約の下で、Ruby のオブジェクト(主にクラスや特異クラス)が見かけ上ではなく本当に属していクラスを知るには evil-ruby に yugui さんのパッチを当てることで利用できる #actual_class、#actual_superclasss を使うのが良さそうです。ただ残念ながら、手元の環境では evil-ruby が 1.9 でうまく動かなかったので、1.8 で調べた結果をもとに、あとは手探り(適当なクラスへのメソッドの追加とそれを起動可能か否かのチェック)で 1.9 での違いがないか確認しつつ(yugui さんも書いておられるとおり両者に BasicObject 以外の違いはなかったわけですが…)まとめた結果が次の図です。
http://www.rubyflow.com/p/948That information is old. The hierarchy was already changed. Thank sumim for the article. it helped us to decide the new hierarchy.
I’ll post an article describing the new hierarchy.
Yugui ― 26 September 2008
黒矢印が クラス←インスタンス関係を、赤矢印が継承パスを示しています。最初の一階層だけ黒い矢印を上り、あとはそこから連なる赤い矢印を連続してたぐることで、メソッドサーチを脳内でシミュレートできます。なお、グレイはそれが特異クラス(あとで示す Squeak Smalltalk ではメタクラス)であることを示しています
#actual_* を使って調べるまでは、#class 、#superclass の結果につられて Smalltalk とそっくりの構成で四層を成す…などと思い込んで書いてしまっていたのですが、実際にはずいぶんと違っていて BasicObject、Object、Module とそれ以外のクラスでは階層がシフトしたり同じ階層にクラスと特異クラス(メタクラス)が混在したりして、Smalltalk よりずっと雑然としていることが分かりました。
参考まで、Squeak Smalltalk ではこんな感じ。
ちなみに Ruby でも、特異クラスが #class で返す結果に従って描くと、見かけ上は階層ごとに整理された Smalltalk のそれとよく似た結果が得られます。
Ruby1.9 の場合
Ruby1.8 の場合
ただ、これには落とし穴があって、たとえば、Ruby1.8 の場合、Kernel のクラスである #
class Object def singleton_class; class << self; self end end end Kernel.singleton_class #=> #<Class:Kernel> Kernel.singleton_class.superclass #=> #<Class:Module> Kernel.singleton_class.superclass.superclass #=> Class
def Module.m1; end Kernel.m1 #=> NoMethodError
class Class; def m2; end end Kernel.m2 #=> NoMethodError
ほかにも、特異クラスを持つ Foo のインスタンスが、見かけ上は #
class Foo; end; foo2 = Foo.new foo2.singleton_class #=> #<Class:#<Foo:0x7ff6e0a8>> foo2.singleton_class.superclass #=> #<Class:Foo> foo2.singleton_class.superclass.superclass #=> #<Class:Class>
#class や #superclass が正確な情報を返さないのにはいろいろと事情があると思うので、それはそれとして他にも evil-ruby + yugui パッチのような機能が組み込みであると、Ruby の謎の動きの解明が簡単にできるのでよいのではないかなと思いました。