世界線航跡蔵

Mad web programmerのYuguiが技術ネタや日々のあれこれをお送りします。

2008年04月09日

Rubyのメタクラス階層

この記事は、先日開催した第3回 RHGの逆襲のまとめみたいなものである。と、同時に『初めてのRuby(仮題)』の宣伝である。

クラス、その例外、そのトリック

Rubyはクラスベースのオブジェクト指向だから、メソッドの情報はクラスに属している。インスタンスメソッドを呼び出すときには、そのオブジェクトの属するクラスを調べて、そのクラスの持っているインスタンスメソッドの中から探す。

でも、Rubyの場合は例外がある。1つはモジュール。モジュールはクラスではないのに、でもインスタンスメソッドを提供している。1つは特異メソッド。特異メソッドは特定のインスタンスに直接所属するメソッドだ。

でも、ここには実装上のトリックがある。Rubyにとってはモジュールのインスタンスメソッドも特異メソッドも、等しくクラスに属するインスタンスメソッドなんだな。

モジュール

モジュールをクラスにincludeすると、Rubyは内部的にモジュールの「身代わり」となるクラスを作成する。RHGではこれを「化身クラス」と呼んでいるので、以下ではそれにならう。

化身クラスはメソッド表(名前からメソッド本体を参照するHash)をモジュールと共有している。だから、モジュールが提供するのと同じインスタンスメソッドを提供していると言える。

includeが行われたタイミングで、Rubyはクラスの継承ツリーにこの化身クラスを挟み込む。

class Cattle; end
class Yapoo < Cattle
  include FlowerGlowable
end

みたいなケースを考えよう。今、モジュールFlowerGlowableの化身クラスをFlowerGlowable′と書くことにする。

includeするまではYapooの親クラスはCattleだ。includeすると、Cattleの子はFlowerGlowable′FlowerGlowable′の子がYapooという構造になる。この構造は、Yapoo.ancestorsみたいなのを実行すると見て取ることができる。

20080413-module.png

特異クラス

特定のオブジェクトだけ、他の同輩たちには無い特別なメソッドを持たせたいと思ったらどうするだろう。特異メソッドとは要するにそういうものだ。

C++だったら、特別なメソッドを持った子クラスを作ってSingletonパターンを適用するというやりかたが選択肢に挙がるだろうなと思う。Rubyの特異クラスとはそれ、そのものだ。

クラスYapombのインスタンスkayoに特異メソッドを定義すると、Yapombを継承した新しいクラス(kayo)を作る。そして、新しいクラスにインスタンスメソッドを定義する。これが特異メソッドの正体である。

それからRubyは、インスタンスkayoが所属するクラスを新しいクラス(kayo)に書き換える。そして、ここでいう新しいクラス、のことを特異クラスと呼ぶ。特異クラスをinspectするとRubyでは#<Class:#<Yapomb:0xXXXXXX @name="kayo">>みたいに表示されるけれども、ここではRHGの表記に倣って(kayo)のように表記する。

before

20080413-eigenclass-before.png

after

20080413-eigenclass-after.png

特異クラス地位向上運動

特異メソッドを実装するのに特異クラスを使う必要はないし、Matzは当初は他の案も検討していたらしいと聞いた。例えば、直接オブジェクトにメソッド表を持たせても良いわけだ。

つまり、特異クラスという存在はMatz's Ruby Implementationの実装の詳細であり、Rubyの公式な仕様ではない。RHGにはそう書いてある。

でもなー、特異クラスは非公式という前提は特異メソッド定義式を導入した時点で破綻していると思う。既に、私みたいな特異クラス好きの間ではclass << obj; self end として特異クラスを取り出すイディオムは常識なわけで、Railsでも随分使われている。そこで、[ruby-dev:34191]みたいな提案も出てくるわけだ。

クラスメソッド

ついでに言えばクラスメソッドとは、クラスの、Classオブジェクトとしての特異メソッドである。別の言い方をすればClassクラスを継承した特異クラスのインスタンスメソッドである。

クラスの特異クラス、これはクラスのクラスだからメタクラスと呼んでも差し支えない。Ruby界の英語ではmetaclass, singleton class, eigenclassという述語が混乱しているけれども、私としては次のように提唱したい。

  • 任意のオブジェクトに対して、その特異クラスのことを英語でeigenclassと呼ぶ
  • Classオブジェクトのeigenclassのことをmetaclassとも呼ぶ

メタ、メタ、メタ

metaclassもまたオブジェクトであり、特にkind_of? Classである。では、metaclassのクラスは? つまり、metametaclassの正体は何者か。

この意味では星さんの記事は誤解を与える恐れがある。

class << obj; end

この式の意味するところは、

  • objの特異クラスの定義を開始する
  • objが特異クラスを持っていなければ(普通のクラスに直接属しているなら)、新たに特異クラスを作成する

だ。

従って、それまで独自の特異クラスを持っていなかったオブジェクトも特異クラスを持ってしまう。つまり、今オブジェクトが真に属しているクラスはこのイディオムでは取得できない。

ちなみに、真に属する、とか言ってるのは、Object#classClass#superclassのようなメソッドは特異クラスや化身クラスをスキップしてその親クラスを返すせいだ。これらの特殊なクラスはRubyレベルでは原則として目に見えなくて、操作できないのだ。

evil-ruby

evil-ruby というgemがある。DLライブラリの力を借りてスーパークラスの書き換え、所属しているクラスの書き換えと言ったRubyが禁止している動作を無理矢理実現してしまう邪悪なgemだ。

これを使えば「真の」所属クラスも取得できるんじゃね? と、RHGの逆襲 第3回で言った。

でも、今調べたらその機能はなかったのでevil-rubyに<a href="http://yugui.jp/files/uploaded/20080413.evil.rb.patch">パッチ</a>を書いた。あとで本家に送っとく。次のメソッドを足した。

  • 「真の」所属クラスを取得するObject#actual_class
  • actual_classを使ってメタクラス階層を遡っていくObject#classification
  • 「真の」スーパークラスを取得するModule#actual_superclass
  • それを使って「真の」先祖を取得するModule#actual_ancestors

evil-rubyは1.9対応が完全ではないので、ついでにClassクラスの処だけ直しておいた。

メタ階層連鎖

修正版evil-rubyと次のスクリプトを使って階層を辿った。

require './evil'
class Cattle; end
class Yapoo < Cattle; end

[Yapoo, Class, Object].each do |start|
  puts "#{start.inspect}:"
  puts "\tancestors:"
  start.ancestors.each do |klass|
    print "\t\t#{klass.inspect}, meta's:"
    p klass.classification
  end
  puts "\tmeta's"
  start.classification.each do |klass|
    print "\t\t#{klass.inspect}, ancestors"
    p klass.actual_ancestors
  end
end

オブジェクト-クラス-メタクラス-メタメタクラスの階層を辿るとこうなる。これはRuby 1.8の場合だが、1.9の場合でもBasicObjectが入るだけで基本的に変わらない。

20084013-metaclass.png

つまり、初期状態ではメタメタクラスというものは定義されていないんだな。メタクラスに対して特異メソッドを定義しようとしたとき、初めてメタメタクラスみたいなものが作成されて、その場合は星さんの記事の通り1.8と1.9で挙動が異なる。

宣伝

……というようなネタを交えつつRuby入門するための入門書『初めてのRuby(仮題)』を書いた。さすがに、入門書なのでここまで深く突っ込んではないけど、その分だけ解説も上記よりは少し丁寧なはず。

で、本はオライリー・ジャパンより動物本の1冊として6月に刊行される予定である。動物は未定みたいだけど、Rubyコミュニティから希望を出したら反映される余地ってあるのかな? 聞いてみよう。

それにしても、一昨日脱稿してようやく自分がいかに緊張していたか気がついた。初の書籍執筆だもん。正直、今になって「あー、こっちの解説のほうが良かった」とか言う点も出てきている。もう少し肩の力を抜いて書ければ良かったかな。

これらの改善点は可能なら初校段階で入れるし、「(ただでさえスケジュール圧してるので)無理」と言われたらこのサイトでフォローアップしていくつもり。

トラックバック

http://yugui.jp/articles/768/ping
[Ruby] 第 3 回 RHG Strikes Back に参加して Ruby の不思議な実装を見た (星一の日記)
第 3 回 RHG Strikes Back に参加しました。ちょっとだけ発表もさせていただきました。 今回は第 4 章のメタクラスあたりについて。複雑すぎです。 以下、得られた知見。 クラスの特異クラスの特異クラスが 1.8 と 1.9 で変わった RHG には「特異クラスの特異クラスは自分自
Ruby1.9 のクラスのメタ階層とかevil-rubyとか (ratio - rational - irrational)
sumimさんの「Ruby1.9のクラスのメタ階層を整理する」という記事、Rub...

コメント

blog comments powered by Disqus

ご案内

前の記事
次の記事

タグ一覧

過去ログ

  1. 2016年07月
  2. 2016年01月
  3. 2015年09月
  4. 2015年08月
  5. 過去ログ一覧

フィード

フィードとは

その他

Powered by "rhianolethe" the blog system