メタプログラミングの基本とか
Rubyはメタプログラミングができる言語だ。
高度なことをしようと思うと特異クラスなどの詳細を知る必要が出てくるが、そういうことをそれなりに知っている人でなければ、特異メソッドぐらいは理解できても特異クラスが出てきた時点でよくわからなくなってしまう。
よくある説明とは違う切り口で説明してみよう。
ちょっと長いが気にしない。
1.
まず、Rubyでは先頭が大文字の識別子は定数である。
このルールで最もよく使われるのはクラスやモジュールの名前だろう。
これらは定数である。
class Hoge end p Object.const_get("Hoge").name # => "Hoge" p Object.const_get("Hoge").class # => Class
Hogeという定数が定義され、そこにHogeクラスを表すClassオブジェクトが入っているわけだ。
つまり上記の定義は(すでにHogeクラスが定義されているのでなければ)以下と同義である。
Hoge = Class.new p Object.const_get("Hoge").name # => "Hoge" p Object.const_get("Hoge").class # => Class
2.
Rubyのオブジェクトはインスタンスメソッドを持つ。インスタンスメソッドは呼び出し時にselfが切り替わるメソッドだ。
Rubyのコードはなんらかの構文と代入式を除けばほぼすべてがメソッドの呼び出しとなる。メソッド呼び出しは「レシーバ.メソッド名」という書き方になる。
ピリオドの左側にはオブジェクトがきて、このオブジェクトのことをレシーバと呼ぶ。メソッドを呼び出すメソッドが__send__という名前であることからも、概念的にはオブジェクトに対してメッセージを送るというイメージに近い。
Rubyではメソッド呼び出しにはレシーバとなるオブジェクトが必須となっている。つまり、メソッド呼び出しはすべて何らかのオブジェクトのインスタンスメソッドの呼び出しであり、selfの切り替えが必ず発生する。
レシーバを省略したメソッド呼び出しはselfに対するものとみなされ、トップレベルで省略した場合はmainという名前がついたObjectクラスのインスタンスがレシーバとなる。
3.
Rubyについてよくある説明に、「すべてがオブジェクト」「クラスもオブジェクト」というものがある。
クラスがオブジェクトであり、クラスに対するメソッド呼び出しも結局はインスタンスメソッドの呼び出しに過ぎないのであるから、以下のコードの
class Hoge end obj = Hoge.new
HogeはClassオブジェクトであり、newというのはClassクラスに定義されたClassオブジェクトのインスタンスメソッドである、と言える。
Classオブジェクトのインスタンスメソッドのことを、クラスメソッドと呼ぶ。
4.
Hogeクラスにメソッドを定義してみよう。
class Hoge def foo p "a" end end obj = Hoge.new Hoge.foo # => undefined method `foo' for Hoge:Class (NoMethodError) obj.foo # => "a"
Hogeクラスにメソッドfooを定義しても、定数Hogeで表されるClassオブジェクトから呼び出すことはできない。
これはfooが、HogeというClassオブジェクトがもつインスタンスメソッドなのではなく、Hoge.newした時に生成されるオブジェクトにくっつけてインスタンスメソッドにするために、Hogeがデータとして保持しているメソッド定義だからである。そのデータを作るのがdefという定義文なのだ。
すべてのオブジェクトが持つ「呼び出し可能なインスタンスメソッド」と、Classオブジェクトが持つ「newしたオブジェクトにくっつけるためのデータ」を、きちんと区別して捉えることができれば、たとえばRubyのモジュールというものが何なのか、なども自然に理解できるようになるだろう。
ここまで前置き。
ここから本題。
5.
クラスメソッドがClassオブジェクトのインスタンスメソッドであるなら、それが定義されているのはClassクラスということになる。
しかし、これをよく考えてみると、Classクラスに定義されたメソッドはすべてのClassオブジェクトのインスタンスメソッドとなるわけで、たとえばHogeクラス独自のクラスメソッドを定義しようとした場合に困る。
インスタンスメソッドをHoge(単体のClassオブジェクト)のみに限定して定義できなければ、オブジェクト指向的なクラスメソッドは実現できないのだ。
で、普通にオブジェクト指向的に考えると、ClassクラスからClass.newしてHogeを作るのではなく、ClassクラスからEigenHogeClassを派生して、そこにメソッドを定義し、EigenHogeClass.newでHogeクラスを生成する手順となるのではないかと思う。
class EigenHogeClass < Class # => can't make subclass of Class (TypeError) def foo p "a" end end Hoge = EigenHogeClass.new obj = Hoge.new obj.a
イメージとしてはこんな感じになるが、このコードは動かないし、クラスメソッドを定義するたびにこんなことをしていては面倒くさすぎる。
Hogeクラスのクラスメソッドを定義するのが目的なのだから、EigenHogeClassはただ必要だからという理由で存在するクラスであり、そもそも名前をつける意味も無い。
で、上記の書き方ができない代わりに、Rubyでは名前を省略しつつもっと簡単に記述するための構文が用意されているわけだ。
Hoge = Class.new class << Hoge def foo p "a" end p self #(1) => #<Class:Hoge> p self.equal?(Hoge) #(2) => false p self.equal?(Class) #(3) => false p Hoge.kind_of?(self) #(4) => true end Hoge.foo # => "a" p Hoge.class #(5) => Class
class << Hogeとendの間のスコープ内では、selfは(1)#
このときのselfの実体は「HogeのクラスであるClassクラスから派生した名前の無いクラス」で、Classの代わりにHogeのクラスとして差し替えられるものである。ようするに、さっきのコードでいうところのEigenHogeClassと同じ位置にあるもので、さっきは上から順に作ったが、今回はあとから間に挟みこむ感じになる。(4)kind_of?がtrueになるところからも、Hogeがselfのインスタンスであることを示している。
さらに、最後の一行(5)で確認したHogeのクラスはClassである。さきほどの#
このRubyレベルで微妙に隠されたヒミツの名前無しクラスのことを、Ruby用語で「特異(eigen)クラス」と言う。
6.
ところで、上の書き方class << Hogeではなんだか面倒だし、隠されているはずのクラスに対して明示的な定義をしたりするのも気持ち悪い。
わざわざ隠すぐらいなのだから、特異クラスを完全に意識しなくてよい構文というのも、やっぱりきちんと用意されている。
class Hoge def Hoge.a end def self.b end end def Hoge.c end
Rubyでクラスを扱ったことのある人であれば見たこともあるだろう。これはクラスメソッドの定義だ。原理は上で説明したこととまったく同じで、内部で特異クラスが作られて、そこにメソッドが定義され、クラスが差し替えられる。
でも特異クラスは隠されているから、p Hoge.classとしてもClassとしか表示されない。したがって、この書き方をした場合、特異クラスという存在自体がまったく表に出てこない。
また、この書き方ではピリオドの左に書くのはオブジェクトである。クラスはオブジェクトであり、Rubyのオブジェクトはすべて平等なものであるから、ここにClassオブジェクト以外のオブジェクトを記述することもできる。
obj = [1,2,3] def obj.delete_all self.clear end p obj.class # => Array obj.delete_all p obj # => [] p [1,2,3].delete_all # => undefined method `delete_all' for [1, 2, 3]:Array (NoMethodError)
obj([1,2,3])に対して、それが所属するクラス(Array)を継承した特異クラスを内部で生成し、objのクラスをそれに差し替え、その特異クラスにdelete_allを定義する。原理・動作はずっと上のコードと同じだ。
んで、この特異クラスはヒミツにされているので、objのクラスはArrayのままであり、見た目・動作から判断すれば、オブジェクトそのものに直接メソッドを追加定義しているように、見える。
このように特異クラスに定義されることで、オブジェクトそのものに個別に定義したように見えるメソッドのことを、Ruby用語で「特異メソッド」と言う。
7.
つまり、特異メソッドというのは特異クラスありきであって、単体で存在できるものではない。隠された特異クラスの存在を把握せず、特異メソッドだけ理解するというのは片手落ちもいいところで、抽象的な理解にならざるを得ない。そこからの延長で特異クラスまで理解できるかと言うと、それはずいぶんチャレンジングな試みな気がしてならない。
説明がヘタクソなのでよくわからなかったかもしれないが、特異クラスが存在する理由や、特異メソッドの位置づけ、関係がぼんやりとでも見えてくれれば、いろいろな仕組みがわかるようになってくるだろう。
このへんの話が少しでもわかれば、ちょっと前にあった東京Ruby会議03のyuguiさんの話にもついていけるかもしれない。
http://www.ustream.tv/recorded/5069318
また、俺はそういう機能を使ったメタプログラミングをする予定は今のところないが、非常に面白いとは思うので、挑戦してみる人が増えるといいな、とも思う。