id:m-hiyama:20080109:1199863428 を Ruby で


せっかくなので Ruby(1.9)についても調べてみました。結論から書くと、RubySmalltalk と同じく四階建てで、その造りもそっくりでした。この絵が網羅している範囲に限れば、両者はほぼ“瓜二つ”。


http://squab.no-ip.com/collab/uploads/61/metaruby.png


ただ、隠し事をしない Smalltalk と違って、何もかもさらけ出してしまうことを必ずしも佳しとしない Ruby では、表面上は三階建てのフリをしている and/or 特異クラスが不可視化されているせいで、上図のような実情どおりにはユーザーには見えません。ちなみにどんな“フリ”をしているかは次図に。


http://squab.no-ip.com/collab/uploads/61/metarubypseudo.png


前後しますが、Ruby には「特異クラス」という、オブジェクト特異的な通常は不可視の無名クラスをアドホックに作れる(つまり元のオブジェクトはそれまでのクラスのもとを離れ、新しく作られた特異クラスのインスタンスとなる…)機構があり、これをインスタンス特異的なメソッド(Ruby ローカルな用語では「特異メソッド」)の裏方として機能させています。また、この特異クラスをクラスオブジェクトにも用いることで Smalltalk 的なメタクラスも実現しています。


しかしこの特異クラスへのアクセスは、Smalltalkメタクラスのように簡単にはいきません。もちろん、Smalltalk 同様 Ruby にもオブジェクトの属するクラスを返す #class というメソッドはあるのですが、このメソッドでは、あるオブジェクトが特異クラスに属していたとしても、ユーザーはその事実を知ることができないのです。なぜなら、特異クラスに属するオブジェクトは、もともと通常のクラスに属していたオブジェクトであれば特異クラスを作る前に属していたもとのクラスに属し続けているかのように、クラスオブジェクトであれば Class に属しているかのように、それぞれが振る舞うからです。

obj = Object.new
obj.class      #=> Object

class << obj; def hoge; end end
obj.class      #=> Object
Object.class   #=> Class


そんなわけで、特異クラスにアクセスするのには、継承ツリーのトップに近いクラスかモジュールに次のようなメソッドを定義する必要があります。

def uniclass
  class << self
    self
  end
end


この #uniclass メソッドは、レシーバの特異クラスがあればそれを返し、無ければ新しく定義して返してきます(特異クラスがなければ nil を返すメソッドを定義できればよかったのですが、あいにく現在の Ruby ではそういうことはできないようです)。

obj.class         #=> Object
obj.uniclass      #=> #<Class:#<Object:0x548170>>
Object.class      #=> Class
Object.uniclass   #=> #<Class:Object>


以上をふまえて、各関係を確認するためのスクリプトは以下に。

class BasicObject; def uniclass; class << self; self end end end
class Person; def initialize(name); @name = name end end

s1 = "Hello"
s2 = "Bye-bye"
p = Person.new('tonkichi')

String.superclass                               #=> Object   /*  1 */
String.uniclass.superclass == Object.uniclass   #=> true
Person.superclass                               #=> Object   /*  2 */
Person.uniclass.superclass == Object.uniclass   #=> true
Class < Object                                  #=> true     /*  3 */
Class.uniclass < Object.uniclass                #=> true
Class.uniclass < Object                         #=> true
s1.class                                        #=> String   /*  4 */
s2.class                                        #=> String   /*  5 */
p.class                                         #=> Person   /*  6 */
String.uniclass                       #=> #<Class:String>    /*  7 */
String.uniclass.uniclass              #=> Class
Person.uniclass                       #=> #<Class:Person>    /*  8 */
Person.uniclass.uniclass              #=> Class
Object.uniclass                       #=> #<Class:Object>    /*  9 */
Object.uniclass.uniclass              #=> Class
Class.uniclass                        #=> #<Class:Class>     /* 10 */
Class.uniclass.uniclass               #=> Class