Ruby1.9 のクラスのメタ階層とかevil-rubyとか

sumimさんの「 Ruby1.9のクラスのメタ階層を整理する 」という記事、Rubyの型階層は雑然としているというのは、なんかsumimさんの図が悪いような気もするなぁ。整理すればもうちょっと情報を引き出せるよ。あと、モジュールのせいもある。

前提知識

Rubyは基本的には単一継承のクラスベースオブジェクト指向言語なのだけれども、幾つか注意すべき処がある。

  • クラスはClassクラスのインスタンスである。
  • モジュール

    • 制限付きの実装多重継承をもたらす仕組み。内部的には、モジュールの「化身」となるクラスを継承階層を挟み込むことで実装されている。
    • 以下、モジュール M に対してその化身クラスを I(M) と表記する。
    • 詳しくは 以前の記事 を参照。
  • 特異クラス

    • 特定のオブジェクトに専属するクラスのこと。特定のオブジェクトにだけ存在するメソッド「特異メソッド」を定義すると、内部的には特異クラスを生成してそれをクラス階層に挟み込んで表現する。
    • 以下、オブジェクト obj に対してその特異クラスを (obj) と書く。

なお、sumimさんの記事では特異クラスは #<Class:<obj>> のように表記されている。これは特異クラスの inspect が返す文字列表現に由来する表記だが、長いので私はRHG式表記を使う。

  • クラスはモジュール

    • クラスはモジュールの一種でもある。どうしてこういう設計になっているかは 別記事 参照。
  • 隠蔽

    • 化身クラスや特異クラスというのはRuby内部の実装の詳細の話だ。モジュールや特異メソッドを実現するためにそういうやり方をしているという話に過ぎない。だから、これらはRubyレベルには出てこないようになっている。
    • ただし、特異クラスは隠蔽漏れがあって、 (class << obj; self end) というイディオムで取得できる。

詳しいことは 以前の記事 を参照。

拡張evil-ruby

で、隠蔽された特異クラスや化身クラスを無理矢理取り出すために、前掲の記事のときに evil-ruby を拡張した。

拡張した分は本家にパッチ送ったんだけど反応がないし、そもそも開発されている気配がない。そこで、今回 githubにフォーク した。

対象ソースと解析

次のソースをRubyに読ませて、evil-rubyで解析してみた。

class Yapoo; end

class MenseMidget < Yapoo; end


rin = Yapoo.new

kimiko = Yapoo.new

anonymous_midget = MenseMidget.new

class << anonymous_midget; end

20080914-metaclass-hierarchy0.png

sumimさんの図よりは、Rubyのクラス階層も幾分ましに見えるんでないか?

見れば分かるけれども、化身クラスは instance_of? Object ではない。それ以前に、Rubyレベルでこのクラスをオブジェクトとして処理する可能性をRubyは考慮していない。だから、化身クラス周りでメソッド呼び出しをするとすぐにRubyが落ちる。どうしようもない部分はgdbで内部を覗いて補いつつなんとかして作ったのが上の図だ。

図の整理

この図をsumimさんの記事のSmalltalkの場合の階層図に合わせて配列してみるとこうなる。

20080914-metaclass-hierarchy1.png

随分とすっきりした。そして、Smalltalkでは4階層になっているところ、Rubyではクラスのクラスのクラスが再び Class のクラスに戻ってくるので2階層になっていることが分かる。言い換えると、これはメタクラスはクラスの一種であるということだ。

モジュール

まあ、先ほどの図には抜けている部分があって、Kernelモジュールがない。Rubyの場合はモジュール機構があるから、この2階層の他にモジュールの階層ができるわけだ。

先ほどの図にモジュールを差し込んでみると次のようになる。化身クラスは書かずに include で表してある。

20080914-metaclass-hierarchy2.png

=begin

複雑になったのは、モジュールをクラス継承と一緒の面に書くから悪いんだと思う。モジュール階層はモジュール階層で別個のレイヤーをなしていて、だから3次元に書くのが正しい。間の2行を上にごそっと持ち上げれば、綺麗になるはずでしょ?

ちなみに、sumimさんのSmalltalk図風にもうちょっと整理したらこうなった。

20080914-metaclass-hierarchy3.png

いや、ModuleクラスやClassクラスを、この下のほうの行に置いてしまうのは妥当か分からないのだけれども。sumimさんの図はSmalltalkにクラスとメタクラスの区別があるのを踏まえて、それで振り分けているように見える。Rubyではメタクラスは特異クラスの一種なのでクラスなのだが、一応、次のように分類した。

で、上から順にメタクラス、狭義のクラス、インスタンス、と並べてある。

思ったこと

(BasicObject)(Object) のクラスが (Class) ではなくて Class だというのは謎だ。これが (Class) になっていれば、ちょうどSmalltalkの4階層が、メタクラスをクラスにしたことで2階層に潰れた形になって綺麗なのだが。実際、こうなる。

def Class.foo

  p :foo

end

Class.foo                   #=> :foo

metaBasicObject = (class << BasicObject; self end)

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

metaBasicObject.foo         #=> NoMethodError

(BasicObject)Class のサブクラスなのに、 Class のクラスメソッドを継承していない!

どうしてこうなっているかというと、たぶん、最初にクラス階層を初期化するときの順番の都合ではないかと予想される。直そうと思えば直せるけれども、普通メタクラスをそんなにいじくらないので誰も文句を言わなくて、なので実装の都合で放置されているんじゃないかと推測する。

モジュールの階層

ついでにモジュールの階層だけ別個に書いてみるとこうなる。次のソースを読み込んだと思いねぇ。

module Inteligence; end

module Prayable

  include Inteligence

end


class Cattle; end

class Pegasus

  include Inteligence

end

class Yapoo

  include Prayable

end

Yapoo の定義が最初と違うのは趣味に走ったら例がかぶっただけなので、気にしない方向で。

20080914-metaclass-hierarchy4.png

ちなみに、モジュールが Module クラスの直接のインスタンスであるのは「初期状態」の話だ。

class << Inteligence; end

と、メタクラスに触ったり、

module Inteligence

  def self.conscious?(subject = self)

    # do something

  end

end

と、クラスメソッドを定義したりすると特異クラス (Inteligence) が湧いてきて、 Kernel , (Kernel) , Module の関係と同じ形になる。

結論

Rubyの階層はSmalltalkよりも雑然として見える。

  • sumimさんの図が悪い。でも、それだけが原因じゃない。
  • メタクラスがクラスでもあるせいで、早めに再帰的な矢印が戻ってきてごちゃごちゃする。
  • モジュールという別のレイヤーのものを書き込まなければならないのでごちゃごちゃする。
  • 化身クラスまわりを真面目に書くと、化身クラスはあちこちに発生するのでごちゃごちゃする。
  • 三次元で、継承階層とmix-in階層を別の方向に分けて描くとたぶんすっきりする。
  • Ruby 1.9が、 メタクラスの特異クラスをClassにしてしまった のは、クラスとメタクラスを区別すると言うことだ。クラスはオブジェクトとして振る舞って結構だがメタクラスをクラス扱いするな、ということだ。これはSmalltalkへの先祖返りか?

宣伝

……というような話を、もうちょっと実用的な範囲に限定しつつもうちょっと丁寧に説明したのが『 初めてのRuby 』の8章です。よろしく。