Rubyのクラスモデル

前置き

rubyのクラス、モジュール、特異クラスについてまとめます。rubyのバージョンは1.9です。

「メタプログラミングRuby(パオロ・ペロッタ著、アスキー・メディアワークス発行)」を参考にしました。

本文

コードベース

実験台として、下記のコードをclassmodel.rbとして保存しておきます。

class SpecialString < String
end

module Fooable
end

module Barable
end

class MyString < SpecialString
  include Fooable, Barable

  def say
    self
  end
end

irbでロードして、実験開始です。

$ irb --simple-prompt
>> require './classmodel'
=> true

オブジェクト(インスタンス)

オブジェクトを生成してみましょう。

>> s = MyString.new "hello"
=> "hello"
>> s.class
=> MyString
>> s.say
=> "hello"

Object#class()は、そのオブジェクトが所属するクラスを返します。ここでは、sがMyStringクラスに所属することが分かります。これを、「sは、MyStringオブジェクトである」と表現することにします。

クラス101

rubyでは全てがオブジェクトなので、クラスもオブジェクトです。オブジェクトなので、Object#class()が使えます。

>> MyString.class
=> Class
>> SpecialString.class
=> Class
>> String.class
=> Class

このように、クラスはClassクラスに所属しています。前節の表現法で言えば、クラスは、Classオブジェクトです。

つまり、sもMyStringもSpecialStringも、オブジェクトである、という点に関しては共通です。ただMyStringやSpecialStringは、オブジェクトであると同時にクラスでもある、というわけです。また大文字で始まっているので、定数オブジェクトですね。

クラス継承(Part1)

冒頭のコードから、見た目通りにクラス継承図を描くと、こんな感じになります。

classd1.png

irbで確認してみましょう。クラスはClassオブジェクトなので、Classクラスのメソッドを使うことができます。親クラスを調べるには、Class#superclass()を使います。

>> MyString.superclass
=> SpecialString
>> SpecialString.superclass
=> String
>> String.superclass
=> Object
>> Object.superclass
=> BasicObject
>> BasicObject.superclass
=> nil

クラス図を修正する必要があるようですね。

classd2.png

モジュール

クラス同様、モジュールもオブジェクトです。Object#class()で調べれば、Moduleオブジェクトだということが分かります。

>> Fooable.class
=> Module
>> Barable.class
=> Module

クラス継承(Part2)

ClassクラスやModuleクラスの継承関係も見ておきましょう。

>> Class.superclass
=> Module
>> Module.superclass
=> Object
>> 以下省略

クラスはモジュールの一種なんですね。

classd3.png

このModuleクラスには、クラス継承やモジュールのインクルード関係を調べるメソッドが定義されています。例として、Module#ancestors()とModule#included_modules()を使ってみましょう。

>> MyString.ancestors
=> [MyString, Fooable, Barable, SpecialString, String, Comparable, Object, Kernel, BasicObject]
>> MyString.included_modules
=> [Fooable, Barable, Comparable, Kernel]

Class#superclass()で調べたクラス継承とは異なり、Module#ancestors()が返す配列にはモジュールも含まれているという点に注意して下さい。また配列内のクラス/モジュールの順序も重要です。メソッド探索は、この配列に格納された順番に、上位クラス/上位モジュールへさかのぼって行きます。

少し脱線しますが、クラスが複数のモジュールをインクルードするとき、インクルード順がどのようにModule#ancestors()の配列に反映されるかを以下のコードで示します。

irb(main):010:0> module MA; end
=> nil
irb(main):011:0> module MB; end
=> nil
irb(main):012:0> module MC; end
=> nil
irb(main):013:0> module MD; end
=> nil
irb(main):014:0> module ME; end
=> nil
irb(main):015:0> class C; include MA,MB,MC; include MD; include ME; end
=> C
irb(main):016:0> C.ancestors
=> [C, ME, MD, MA, MB, MC, Object, Kernel, BasicObject]

では本題に戻り、MyString.ancestors()の結果に基づいてクラス図を修正しておきましょう。

classd4.png

特異メソッド、特異クラス

いよいよ難関の特異クラスに入ります。正式名は「特異(singleton)」ですが、個人的には「固有(eigen)」の方が適しているような気がします。

全てのオブジェクトは特異クラスを持つことができます。特異クラスは、あるオブジェクトに固有のクラスです。特異クラスに定義されたメソッドは特異メソッドと呼ばれ、そのオブジェクトのみに適用することができます。特異クラスは、必要に応じて(つまり初めて特異クラスが参照されたときや特異メソッドが定義されたときに)生成されます。

特異メソッドの定義方法の一例を示します。

>> def s.eigen_method; say; end    # 特異メソッド。
=> nil
>> s.eigen_method
=> "hello"
>> t = MyString.new "world"
=> "world"
>> t.eigen_method                  # s以外には使えない。
NoMethodError: undefined method `eigen_method' for "world":MyString
        from (irb):28
        from C:/misc/Ruby193/bin/irb:12:in `<main>'

1行で書いてますが、普通に書けば下記のようになります。クラスメソッドの定義方法と似てますね。

def s.eigen_method
  say
end

全く違う定義方法もあります。

>> class << s; def another_eigen; "#" + say; end; end
=> nil
>> s.another_eigen
=> "#hello"

整形すると、こんな感じです。

class << s
  def another_eigen
    "#" + say
  end
end

"class << s"により、オブジェクトsの特異クラスをオープンしていることになります。なので、"class << s"の内部(かつ、defの外部)では、selfが特異クラスを指します。これを利用して、任意のオブジェクトの特異クラスを返す関数を書いてみましょう。

>> def eigenclass(obj); class << obj; self; end; end
=> nil
>> eigenclass s
=> #<Class:#<MyString:0x1114ae8>>

eigenclass()は、トップレベルのグローバル関数みたいなものだと思って下さい(厳密に言えば、Objectクラスのprivateなインスタンスメソッドです)。引数で指定されたオブジェクトobjの特異クラスを返します。

実は、これと同等のメソッドがObjectクラスから提供されています。Object#singleton_class()です。

>> s.singleton_class
=> #<Class:#<MyString:0x1114ae8>>

オブジェクトの特異メソッドを調べるには、Object#singleton_methods()を使います。

>> s.singleton_methods
=> [:eigen_method, :another_eigen]

特異クラスに関して一番大事なことは、メソッド探索のとき、Module#ancestors()が返すクラス/モジュール群よりも先に、特異クラスが探索されるという点でしょう。

>> def s.say; self + "(from eigen method)"; end  # 特異メソッドsay()を定義。
=> nil
>> s.singleton_methods
=> [:eigen_method, :another_eigen, :say]
>> s.say                               # MyString#say()よりも、特異メソッドsay()が優先。
=> "hello(from eigen method)"
>> s.eigen_method
=> "hello(from eigen method)"
>> s.another_eigen
=> "#hello(from eigen method)"

そしてナント、オブジェクトsの特異クラスの親クラスはMyStringなのです。

>> s.singleton_class.superclass
=> MyString

再びクラス図を修正します。オブジェクトの特異クラスを「⇒」で表記してみました。

classd5.png

特異クラスは隠れた存在なので、Module#ancestors()が返す配列に特異クラスは含まれません。

>> s.singleton_class.ancestors
=> [MyString, Fooable, Barable, SpecialString, String, Comparable, Object, Kernel, BasicObject]

クラス201

クラスはClassオブジェクトでした。オブジェクトなので、特異クラスや特異メソッドを持つことができます。前節で示した2通りの定義方法を使って、MyStringクラス(つまりClassオブジェクト)に特異メソッドを定義してみましょう。

def MyString.eigen_method
  "MyString.eigen_method()"
end

class << MyString
  def another_eigen
    "MyString.another_eigen()"
  end
end

前節のsがMyStringに代わっただけです。どちらもオブジェクトなので問題ありません(MyStringはClassクラスのオブジェクトで定数オブジェクトだ、という点がsとは違うだけです)。呼んでみましょう。

>> MyString.eigen_method
=> "MyString.eigen_method()"
>> MyString.another_eigen
=> "MyString.another_eigen()"

まるでクラスメソッドですね。実際、これらはクラスメソッドです。つまりクラスメソッドとは、クラスの(つまりClassオブジェクトの)特異メソッドのことなのです。

あと2つ、クラスメソッドの定義方法を示します。ここでは、classの内部(かつdefの外部)では、selfが当該クラス自身(つまりMyString)を指すという事実を利用しています。

class MyString
  def self.third_eigen
    "third"
  end

  class << self
    def fourth_eigen
      "fourth"
    end
  end
end

4通りとも、微妙に違いますが全て作用は同じ(つまりクラスメソッドを定義している)です。

>> MyString.singleton_methods.grep /eigen/
=> [:eigen_method, :another_eigen, :third_eigen, :fourth_eigen]

さて、sもMyStringもオブジェクトなので、特異クラスを持つことができました。そして、前節で示したように、オブジェクトsの特異クラスの親クラスはMyStringクラス(つまりsの所属先クラス)でした。

eigenclassd1.png

この関係は(オブジェクトとしての)MyStringにも言えるでしょうか? つまり、オブジェクトMyStringの特異クラスの親クラスは、その所属先クラス(つまりClassクラス)でしょうか? 確かめてみましょう。

>> MyString.singleton_class
=> #<Class:MyString>
>> MyString.singleton_class.superclass
=> #<Class:SpecialString>

残念ながら違うようです。#<Class:SpecialString>って何? 結論を言ってしまうと、これはSpecialStringの特異クラスです。

>> SpecialString.singleton_class
=> #<Class:SpecialString>

容易に想像がつきますが、これはMyStringの継承ツリーを上って行きます。そして最終的には……。

>> MyString.singleton_class
=> #<Class:MyString>
>> MyString.singleton_class.superclass
=> #<Class:SpecialString>
>> MyString.singleton_class.superclass.superclass
=> #<Class:String>
>> MyString.singleton_class.superclass.superclass.superclass
=> #<Class:Object>
>> MyString.singleton_class.superclass.superclass.superclass.superclass
=> #<Class:BasicObject>
>> MyString.singleton_class.superclass.superclass.superclass.superclass.superclass
=> Class

ようやく、Classに到達しました。

eigenclassd2.png

これは、Classオブジェクトのみが持つ特殊性のようです。恐らく、クラスメソッドの継承を実現するために、この特殊性が必要なんでしょうね。

>> def SpecialString.special_eigen; "Special!"; end   # SpecialStringにクラスメソッドを定義。
>> SpecialString.singleton_methods.grep /^special/
=> [:special_eigen]
>> MyString.singleton_methods.grep /^special/
=> [:special_eigen]
>> MyString.special_eigen                             # それを派生クラスMyStringが継承。
=> "Special!"

ちなみにModuleオブジェクトにも、Classオブジェクトのような特殊性はありません。Moduleオブジェクトの特異クラスの親クラスはModuleクラスなので、オブジェクトsと同じ関係が成立します。

>> Fooable.singleton_class
=> #<Class:Fooable>
>> Fooable.singleton_class.superclass
=> Module

おまけ

オブジェクトは特異クラスを持つことができ、特異クラスは必要に応じて生成されるということを踏まえると、特異クラスの特異クラスも作れそうです。そして、無限に増殖できるようです。

>> s.singleton_class
=> #<Class:#<MyString:0x1114ae8>>
>> s.singleton_class.singleton_class
=> #<Class:#<Class:#<MyString:0x1114ae8>>>
>> s.singleton_class.singleton_class.singleton_class
=> #<Class:#<Class:#<Class:#<MyString:0x1114ae8>>>>

特異クラスをnewしたら、どうなるんでしょう?

>> ss = s.singleton_class.new
TypeError: can't create instance of singleton class
        from (irb):122:in `new'
        from (irb):122
        from C:/misc/Ruby193/bin/irb:12:in `<main>'

特異クラスのオブジェクトは作れないんですね。

Last modified:2012/05/18 23:18:08
Keyword(s):
References:[役に立つ記事・書籍など] [言語Tips]
This page is frozen.