id:m-hiyama:20080109:1199863428 を Ruby で
せっかくなので Ruby(1.9)についても調べてみました。結論から書くと、Ruby も Smalltalk と同じく四階建てで、その造りもそっくりでした。この絵が網羅している範囲に限れば、両者はほぼ“瓜二つ”。
ただ、隠し事をしない Smalltalk と違って、何もかもさらけ出してしまうことを必ずしも佳しとしない Ruby では、表面上は三階建てのフリをしている and/or 特異クラスが不可視化されているせいで、上図のような実情どおりにはユーザーには見えません。ちなみにどんな“フリ”をしているかは次図に。
前後しますが、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