クラスとモジュール

リフレクション

「オブジェクト」の項ではクラスオブジェクトのことに少しだけふれた。 ここではさらにその先へ進んでいく。まずはもう一度 struct RClass と RBasic を見てみよう。


struct RBasic {
    unsigned long flags;
    VALUE klass;
};

struct RClass {
    struct RBasic basic;
    struct st_table *iv_tbl;
    struct st_table *m_tbl;
    VALUE super;
};

(ruby.h)

struct RClass も struct RBasic を要素に持つので、 RClass.basic.klass がある。ここには何が入っているのだろうか? つ まり ObjectArray それ自体のクラスは? まずは普通に Ruby スク リプトから見てみよう。


p Object.class
p Array.class
p Hash.class
p Class.class

結果はすべて Class になる。つまりこんな感じだ。


Object ---> Class
  |
  |
Module ---> Class
  |
  |
Class  ---> Class

縦の線が継承、横がオブジェクト〜クラスだ。Class のクラスは また Class だから、ここで無限ループができていることになる。 つまりインスタンス〜クラス関係に注目して図を書くとこうだ。


とあるオブジェクト → Object → Class → Class → Class → .....

なぜループしていなくてはいけないか。まず、Ruby ではすべてのデー タはオブジェクトである。そしてまた Ruby ではクラスもデータなの でクラスもオブジェクトである。オブジェクトはメソッドによって定 義されるので、逆に言うとオブジェクトはなんらかのメソッドに反応 できなくてはならない。そして Ruby ではメソッドに反応するために はなんらかのクラスに属さなければいけない。従って、あらゆるオブ ジェクトはどこかのクラスのインスタンスでなければならない。ここ から、クラス自身もクラスを持たなくてはいけないとわかる。

ではこのことを踏まえて現実に実装することを考えてみる。最も単純 で素直なのは、Class のクラスは ClassClass、ClassClass のクラス は ClassClassClass ……というふうに、概念を表現するクラスを次々 と連鎖することである。しかしこれではちょっと実装がめんどくさい。 そんなわけでクラスがオブジェクトであるオブジェクト指向言語は、 Class のクラスを Class 自身にしてしまうことでループを作りだし、 無限のインスタンス〜クラス関係を仮想的に作りだすのが常套手段な のである。

補注:

この二つは切り離して考えなければいけない。クラスがオブジェクト であるのは、あくまで Ruby プログラムに自分自身 (プログラムコー ド) を操作できる能力……リフレクション (reflection) と言う…… を与えるためである。プログラムはデータしか変更できない (または、 変更できるものをデータと言う)。そしてデータは Ruby ではオブジェクト としてしか存在できない。だからクラスがオブジェクトなのである。

特異クラス

さて、じゃあ RClass.basic.klass には Class が入ってるんだね! これで解決!……と思いきや、これが違うのだ。printf を使って見 てみると、RBASIC(rb_cObject)->klass と rb_cClass は違う値にな る。実は RClass.basic.klass に入っているのは「特異クラス」とい うものである。

これがナニか、というのはひとまず置いておいて、まずは現実の姿を 見ることにしよう。Ruby のクラスまわりのオブジェクトは以下のよ うなリンクを形成している。



Ruby class hierarchy
                                      +--------+
                                      |        |
                            Object ---|---> (Object) --->
                            ^  ^      |      ^ ^
                            |  |      |      | |
     +----------------------+  |      |      | |
     |            +------------|------|------+ |
     |            |            |      |        |
 AnyClass --> (AnyClass)-------|------|--------|-------->
                               |      |        |              Class
 AnyModule -> (AnyModule)-> Module ---|---> (Module) --->
                               ^      |        ^
                               |      |        |
                               |      |        |
                               |      |        |       
                             Class ---|---> (Class)----->
                               ^      |
                               |      |
                               +------+

かっこつきのものが特異クラスだ。太い縦の矢印が継承関係で、細い 横の矢印がインスタンス〜クラス関係である(矢印側がクラス)。重要な 点は二つだ。

  1. 全てのクラスオブジェクトはそれと一対一に対応する特異クラスを持つ
  2. 全ての特異クラスは Class の下位クラスであり同時にインスタンスである

またこの図にはもう一つ 「クラスオブジェクトの特異クラスはクラスに対応して継承する」 という重要な特徴が書かれているのだが、これについてはあとで触れる。

特異クラスの生成

まず「特異クラス」なるものの正体を見よう。特異クラスを 作る関数は rb_singleton_class_new() だ。


VALUE
rb_singleton_class_new(super)
    VALUE super;
{
    VALUE klass = rb_class_new(super);

    FL_SET(klass, FL_SINGLETON);
    return klass;
}

(class.c)

rb_class_new() は Class のインスタンスを生成する関数だから、普通の クラスと特異クラスの違いは FL_SET(...) だけということになる。 FL_SETRBasicflags をセットするマクロなので、つまり実装の観 点からは特異クラスとは FL_SINGLETON フラグがセットされたクラスだと 言うことができる。

スクリプトから特異クラスにアクセスする

さて、ではなぜ Class.class が (Class) を返さないかというと、メ ソッド class() が特異クラスをスキップするようになっているからで ある。普通のメソッドは特異クラスを隠蔽するようになっており、普 通にスクリプトを書いている限りなかなか特異クラスにはお目にかか れない。だが、唯一それが露出している部分がある。それは特異クラ ス定義文だ。


class << Object
  self
end

Rubyスクリプト実行中は、コードが書けるところならば必ずメソッドのデ フォルトのレシーバ(self)が存在する。クラス定義中はそのクラスが self になるので、特異クラス定義中は self が特異クラスになっている わけだ。例えば上の例の self(Object) だ。ただし to_sname で 見ても class() の場合と同じように特異クラスがスキップされて "Object" になってしまうので違いがわからない。__id__ を使って確かめよう。

1.7 からは # のように表示され区別できるようになった。

では任意の特異クラスを入手するにはどうすればよいだろうか。あまり知 られていないことだが実はクラス定義にも返り値がある。スコープの最後 の式の値が特異クラス定義の返り値なので、次のようにすれば特異クラス を返すメソッドが定義できる。


def singleton_class_of( obj )
  class << obj; self end
end

p singleton_class_of(Object.new).__id__
p Object.__id__

ただし特異クラス定義の構文は開始した時点で問答無用で特異クラスを作る ため、引数に渡されたオブジェクトすべてに特異クラスを導入してしまう。 あくまで実験して楽しむ範囲にとどめておき、実際に活用したりしないほうがよい。

特異メソッド

特異クラスはそもそも特異メソッドの実装のために作られたものだ。 ここでは実際に特異メソッドが定義される過程を見て、特異クラスについ てさらに理解を進めてゆこう。

まず最初は穏当に rb_define_singleton_method() から始める。


void
rb_define_singleton_method(obj, name, func, argc)
    VALUE obj;
    const char *name;
    VALUE (*func)();
    int argc;
{
    rb_define_method(rb_singleton_class(obj), name, func, argc);
}

(class.c)

rb_define_method() は拡張モジュールを定義するのにも使うごく普通の 関数だから、どうやら rb_singleton_class() というのがキモのようだ。 この関数はすぐ上に定義されている。


VALUE
rb_singleton_class(obj)
    VALUE obj;
{
    if (rb_special_const_p(obj)) {
        rb_raise(rb_eTypeError, "can't define singleton");
    }
    if (FL_TEST(RBASIC(obj)->klass, FL_SINGLETON)) {
        return RBASIC(obj)->klass;
    }
    RBASIC(obj)->klass = rb_singleton_class_new(RBASIC(obj)->klass);
    rb_singleton_class_attached(RBASIC(obj)->klass, obj);
    return RBASIC(obj)->klass;
}

(class.c)

rb_special_const_p()util.c にある関数で、VALUE が即値(ポインタ でない)であるオブジェクトすなわち nilFixnum でに対して真を返す。 これを弾いているのは、つくりを考えれば明らかだろう。対応する構造体 がなければ struct RBasic がないわけで、特異クラスを格納できない。

ということはしかし逆に言うと BignumFloatOK ってことになる。 もっともそんなことをしてもあまり役に立ちそうにはない。

次の if でオブジェクトのクラスにフラグ FL_SINGLETON が立っているか (特異クラスかどうか)テストして、特異クラスだったらそれをそのまま返 す。そうでなかったら、rb_singleton_class_new() で新しく特異クラス を作成し、同時にそれをオブジェクトのクラスにする。このとき、特異ク ラスは現在のクラスの下位クラスとして作られるので、それまでに定義さ れているメソッドは自動的に継承されるわけだ。そのあとの rb_singleton_class_attached() はなんだろうか? 見てみよう。


void
rb_singleton_class_attached(klass, obj)
    VALUE klass, obj;
{
    if (FL_TEST(klass, FL_SINGLETON)) {
        if (!RCLASS(klass)->iv_tbl) {
            RCLASS(klass)->iv_tbl = st_init_numtable();
        }
        st_insert(RCLASS(klass)->iv_tbl, rb_intern("__attached__"), obj);
    }
}

(class.c)

フラグ FL_SINGLETON が立っているのが特異クラスだったから、この場合 最初の if は真だ。どういうときに偽になるのかわからなかったので 検索してみたところ、最新のバージョンだと nilfalse に特異メソッドを 定義できるようになっていて、そのときに偽になるようだ。

さて、if が真になったときは、特異クラスオブジェクトのインスタ ンス変数 __attached__ にそのインスタンスを代入している。実は暗 黙のルールとして「特異クラスはひとつしかインスタンスを持たない」 ことになっているので、特異クラスとそのインスタンスは一対一に関 連付けできるのだ。その対応がここでなされている。

__attached__ にはプリフィクスの@がついていないが、インスタン ス変数テーブルに格納されるのでいちおうインスタンス変数というこ とになる。このようなインスタンス変数は Ruby スクリプトレベルか らは絶対に参照できない変数だ。

またこの __attached__ を参照しているのは主に eval.c のようだ。 特異メソッドが削除されたり追加されたときにフックメソッドを(イ ンスタンスに対して)呼んだり、クラス変数を実装するために使った りしている。

まとめると、rb_singleton_class() は、あるオブジェクトの特異クラスを (必要なら作成して)返すということがわかる。そのあとは通常通りの rb_define_method() で特異クラスにメソッドを定義すれば完了となる。 つまり特異メソッドが普通のメソッドと違うところは定義されるクラスだ けでメソッド側になんら変わりはない。そして特異クラスの定義とは「あ るインスタンス専用のクラスを普通のクラスとの間に一枚はさむ」作業だ と総括できる。

通常クラスの定義

クラスを定義するための基本 APIrb_define_class()rb_define_class_under() の二つである。拡張モジュールはもちろん として、基本クラスライブラリまでがすべてこの二つだけで定義され ている。他に rb_define_class_id() という API もあるが、これは インタプリタ内部用である。前述の rb_define_class() _under() が これを使っているほか、Ruby スクリプトで class 文を書くとインタ プリタがこの関数を呼ぶ。

ではこの三つを読んで具体的に違いを確認していこう。まずは内部に 近い rb_define_class_id() から読み進めていく。


VALUE
rb_define_class_id(id, super)
    ID id;
    VALUE super;
{
    VALUE klass;

    if (!super) super = rb_cObject;
    klass = rb_class_new(super);
    rb_name_class(klass, id);
    /* 特異クラスを作る */
    RBASIC(klass)->klass = rb_singleton_class_new(RBASIC(super)->klass);
    rb_singleton_class_attached(RBASIC(klass)->klass, klass);
    rb_funcall(super, rb_intern("inherited"), 1, klass);

    return klass;
}

(class.c)

rb_class_new() はもう説明したとおり。クラスオブジェクトを生成する (オブジェクトを作るだけで、リンクは形成しない)。

rb_name_class()name は動詞「命名する」だ。知っていると思う がクラスはクラス名と同名の定数に代入され、またクラスから名前を 知ることもできる。その関連付け(の片方)を行うのがこの関数。


void
rb_name_class(klass, id)
    VALUE klass;
    ID id;
{
    rb_iv_set(klass, "__classid__", ID2SYM(id));
}

見てのとおり。__classid__ にクラス名の ID をセットする。 __classid__ もやはりスクリプトからは見えない変数である。

しかし定数への代入は行っていない。だがこの操作が rb_define_class() と……_under() とで違うのは明らか なので、それぞれの関数でやっているはずだ、と推測できる。

さて、その後が問題だ。


    /* 特異クラスを作る */
    RBASIC(klass)->klass = rb_singleton_class_new(RBASIC(super)->klass);
    rb_singleton_class_attached(RBASIC(klass)->klass, klass);

前述したとおりクラスの klass は特異クラスなのだが、その特異ク ラスのスーパークラスは、スーパークラスの klass、つまり特異クラ スだ。ああややこしい。図に書こう。


SomeClassA ---> (SomeClassA)
    |
    |
SomeClassB ---> (SomeClassB)

こういうふうに継承をすると、


SomeClassA ---> (SomeClassA)
    |                |
    |                |
SomeClassB ---> (SomeClassB)

こうなるということだ。つまり「クラスの特異クラスは、クラスの継 承と同じように対応して継承する」のである。まったく同じクラスツ リーが、クラスと、それに対応する特異クラスの両方で形成されるの だ。これは普通の特異メソッド定義では起きない現象で、クラスの特 異クラスの場合だけ起こる。

なんのためにこんなことをするかというと、実はクラスの特異メソッ ドを継承させるためである。もっと言うと Object.new のためである。 Object.new は特異メソッドなので単純に継承しただけでは SomeClass.new が定義されない。これでは不便だ。ていうかそれどこ ろではなく、スクリプトレベルで定義したクラスのインスタンスが全 く生成できなくなる。そういうわけでクラスの特異クラスだけはクラ スツリーと連動するようになっているのである。

それと最後の rb_funcall() は Ruby のメソッドを呼ぶ関数で、ここ では Class#inherited を klass とともに呼んでいる。こんなもん何 に使うのかなぁ、と思ったのだが……使ってた。 rb_class_s_inheritedobject.c)だ。Class の特異メソッド(すな わち (Class) のメソッド)で、デフォルトの実装は例外を発生す るようになっている。つまり Class を継承するのを禁止している。 ということは、このメソッドを再定義してしまえば Class の下位クラスを作れるわけだ。やろう。


def Class.inherited( klass )
end

class AClass < Class
end

p AClass.ancestors

だからどうってこともないけど、禁止されてるとやりたくなるんだよね。

クラス名

ここまででクラスを作ることはできた。あとはクラスとその名前(定数)を リンクする作業だが、これが想像以上にめんどくさい。かなりの長丁場になる。

まずは rb_define_class() だ。


VALUE
rb_define_class(name, super)
    const char *name;
    VALUE super;
{
    VALUE klass;
    ID id;

    id = rb_intern(name);
    if (rb_const_defined(rb_cObject, id)) {
	klass = rb_const_get(rb_cObject, id);
	if (TYPE(klass) != T_CLASS) {
	    rb_raise(rb_eTypeError, "%s is not a class", name);
	}
	if (rb_class_real(RCLASS(klass)->super) != super) {
	    rb_raise(rb_eNameError, "%s is already defined", name);
	}
	return klass;
    }
    klass = rb_define_class_id(id, super);

    st_add_direct(rb_class_tbl, id, klass);

    return klass;
}

大きく rb_define_class_id() の前後に分かれる。前ではすでに 同名のクラスが定義されていないかどうかチェックして、定義さ れていたら追加定義の条件を満たすかどうか確認する。だめなら ハネる。大丈夫ならそれを返して終了(新しいクラスを定義しない)。

後の st_add_direct() が先程言っておいた、定数にクラスを代入す る操作である。しかし、これを見れば明らかなとおり、どう見て も定数に代入しているようには見えない。実はトップレベルのクラス は通常の定数とは別に管理されており、定数を参照するときに rb_class_tbl を追加で検索するようになっているのだ。わざわざ別 になっているのは String などの基本クラスを置き換えられたくな いからであろう。

これでネストレベル 1 の場合の定数・クラス変換はできるようになった。 あとはネストレベル 2 以上の場合で、これはもっと複雑だ。レベル 2 以上の クラスを定義するのは rb_define_class_under() である。


VALUE
rb_define_class_under(outer, name, super)
    VALUE outer;
    const char *name;
    VALUE super;
{
    VALUE klass;
    ID id;

    id = rb_intern(name);
    if (rb_const_defined_at(outer, id)) {
	klass = rb_const_get(outer, id);
	if (TYPE(klass) != T_CLASS) {
	    rb_raise(rb_eTypeError, "%s is not a class", name);
	}
	if (rb_class_real(RCLASS(klass)->super) != super) {
	    rb_raise(rb_eNameError, "%s is already defined", name);
	}
	return klass;
    }
    klass = rb_define_class_id(id, super);
    rb_const_set(outer, id, klass);
    rb_set_class_path(klass, outer, name);

    return klass;
}

構造は rb_define_class() と同じで、rb_define_class_id() 呼びだ しの前が再定義チェック、後が定数←→クラスの相互リンク作成であ る。前半はつまらないのでとばす。後半は rb_set_class_path() が 重要である。


void
rb_set_class_path(klass, under, name)
    VALUE klass, under;
    const char *name;
{
    VALUE str;

    if (under == rb_cObject) {
	str = rb_str_new2(name);
    }
    else {
	str = rb_str_dup(rb_class_path(under));
	rb_str_cat2(str, "::");
	rb_str_cat2(str, name);
    }
    rb_iv_set(klass, "__classpath__", str);
}

最後の一行以外は「クラスパス」を作っている。クラスパスとは例え ば「Net::NetPrivate::Socket」のように、トップレベルからのネス ト情報をすべて含んだ名前のことである。この場合「クラス名」は 「Socket」である。

そして最後の一行で、クラスオブジェクトのインスタンス変数 __classpath__ に代入している。これも __classid__ と同じくスクリ プトからは見えないインスタンス変数だ。

さて、実はこの前半、クラスパスの作成が予想外にやっかいだ。 この関数内の作業はいいとして、rb_class_path() に進んでいこう。


VALUE
rb_class_path(klass)
    VALUE klass;
{
    VALUE path = classname(klass);

    if (path) return path;
    else {
	char buf[256];
	char *s = "Class";

	if (TYPE(klass) == T_MODULE) s = "Module";
	sprintf(buf, "#<%s 0lx%lx>", s, klass);
	return rb_str_new2(buf);
    }
}

(variable.c)

else 節が対応しているのは明らかに名無しクラスの場合だ。 つまり classname() は、クラスがすでに定数に代入されている場合にはその パスを返し、でなければ NULL を返すらしい。続いてその classname()


static VALUE
classname(klass)
    VALUE klass;
{
    VALUE path = Qnil;
    ID classpath = rb_intern("__classpath__");

    klass = rb_class_real(klass);
    if (!klass) klass = rb_cObject;
    if (ROBJECT(klass)->iv_tbl &&
	!st_lookup(ROBJECT(klass)->iv_tbl, classpath, &path)) {
	ID classid = rb_intern("__classid__");

	if (st_lookup(ROBJECT(klass)->iv_tbl, classid, &path)) {
	    path = rb_str_new2(rb_id2name(SYM2ID(path)));
	    st_insert(ROBJECT(klass)->iv_tbl, classpath, path);
	    st_delete(RCLASS(klass)->iv_tbl, &classid, 0);
	}
    }
    if (NIL_P(path)) {
	path = find_class_path(klass);
	if (NIL_P(path)) {
	    return 0;
	}
	return path;
    }
    if (TYPE(path) != T_STRING)
	rb_bug("class path is not set properly");
    return path;
}

(variable.c)

初っ端からたらいまわしである。


VALUE
rb_class_real(cl)
    VALUE cl;
{
    if (TYPE(cl) == T_ICLASS) {
	cl = RBASIC(cl)->klass;
    }
    while (FL_TEST(cl, FL_SINGLETON) || TYPE(cl) == T_ICLASS) {
	cl = RCLASS(cl)->super;
    }
    return cl;
}

(object.c)

T_ICLASS は、後述するとおりモジュールに関連して生成される内部 処理用のクラス。一方の FL_SINGLETON なクラスはもちろん特異クラ スだ。すなわち rb_class_real() は最も近い通常クラスを返す。

とするとしかし、classname()if (!klass) が真になることは ないはずである。なぜなら、rb_class_real()clNULL になったら FL_TEST の時点で落ちるからだ。

では次の if


    if (ROBJECT(klass)->iv_tbl &&
	!st_lookup(ROBJECT(klass)->iv_tbl, classpath, &path)) {
	ID classid = rb_intern("__classid__");

	if (st_lookup(ROBJECT(klass)->iv_tbl, classid, &path)) {
	    path = rb_str_new2(rb_id2name(SYM2ID(path)));
	    st_insert(ROBJECT(klass)->iv_tbl, classpath, path);
	    st_delete(RCLASS(klass)->iv_tbl, &classid, 0);
	}
    }

条件のひとつめの逆、iv_tbl がないというのは、 名無しクラスの場合である。なぜなら名無しでない場合 __classid__ を 代入するために iv_tbl が存在するはずである。 条件のふたつめ、クラスパスがすでにセットされていないというのは、 いま始めて定義するクラスの場合、だろう。 合わせると、この if が真になるのはごくごくフツーのクラスの場合である。

この場合は __classid__ を索いてそれを dup__classpath__ を セット(キャッシュ)して __classid__ を消す。

次の if


    if (NIL_P(path)) {
	path = find_class_path(klass);
	if (NIL_P(path)) {
	    return 0;
	}
	return path;
    }

pathnil になるのは以下の場合だ。

後者二つが成立するのは特異クラスに他ならない。

このあたりはかなり呼び出し関係が深かったので表にしておく。


rb_define_class_under
    rb_set_class_path
        rb_class_path
            classname
                rb_class_real
                find_class_path
                    st_foreach
                        (fc_i)
                            fc_path

クラスツリーの初期化

残るは Object Module Class の三つだ。通常の rb_define_class()class.c)などの通常の API ではこの三つのクラスを作成できない。 というのは、クラスを作るためにはその特異クラスを作る必要がある が、特異クラスを作るためには Class が必要だからだ。しかしもち ろんClass はまだできていない。そこで Ruby はこの三つだけは特別 扱いで生成する。


void
Init_Object()
{
    VALUE metaclass;

    rb_cObject = boot_defclass("Object", 0);
    rb_cModule = boot_defclass("Module", rb_cObject);
    rb_cClass =  boot_defclass("Class",  rb_cModule);

まず前半から。これは boot_defclass が全てを語ってくれる。


static VALUE
boot_defclass(name, super)
    char *name;
    VALUE super;
{
    extern st_table *rb_class_tbl;
    VALUE obj = rb_class_new(super);
    ID id = rb_intern(name);

    rb_name_class(obj, id);
    st_add_direct(rb_class_tbl, id, obj);
    return obj;
}

rb_define_class() などに比べると非常に簡単である。 ここまでで定数・クラスの相互参照が確立し、リンクは 以下のようになっている。


Object --> NULL
  |
Module --> NULL
  |
Class  --> NULL

以下は Init_Object() の続き。


    metaclass = RBASIC(rb_cObject)->klass = rb_singleton_class_new(rb_cClass);
    rb_singleton_class_attached(metaclass, rb_cObject);
    metaclass = RBASIC(rb_cModule)->klass = rb_singleton_class_new(metaclass);
    rb_singleton_class_attached(metaclass, rb_cModule);
    metaclass = RBASIC(rb_cClass)->klass = rb_singleton_class_new(metaclass);
    rb_singleton_class_attached(metaclass, rb_cClass);

特異クラスを生成し直接構造体にねじこむのみである。単純だ。

インクルード

ところで、これまでモジュール(Module クラスのインスタンス)がほとんど 出てこなかった。Module は Class の上位クラスなのに、クラスのほうがたくさん 出てきて偉そうだ。これからモジュールの特徴であるインクルードについて 調べよう。

オブジェクトの章では、それぞれのオブジェクトが ポインタ(VALUE)と構造体の組で表されること、その構造体には自身の クラスを格納するメンバ value->basic->klass があることを知った。 klass はただひとつしかなかったので、Ruby は単一継承らしいとわかる。 しかし実はモジュールのインクルードは概念的には多重継承であり、イン クルードの際に多少加工して単一継承のように見せかけているにすぎない。

では早速その様子を見てみよう。インクルード操作の実体は rb_include_module() だ。……ただし 本当に正確に言うと、rb_mod_inclucde() (eval.c)include の実体で、 そこから Module#append_features が呼ばれ、そのデフォルトの実装が rb_include_module() を呼ぶようになっている。 やや長いので、少しづつ。最初にいろいろとチェック部分があって、


void
rb_include_module(klass, module)
    VALUE klass, module;
{
    VALUE p;
    int changed = 0;

    rb_frozen_class_p(klass);
    if (!OBJ_TAINTED(klass)) {
        rb_secure(4);
    }

    if (NIL_P(module)) return;
    if (klass == module) return;

    switch (TYPE(module)) {
      case T_MODULE:
      case T_CLASS:
      case T_ICLASS:
        break;
      default:
        Check_Type(module, T_MODULE);
    }

以下が本処理。klass に、module をインクルードする。


    OBJ_INFECT(klass, module);
    while (module) {
        /* 既にスーパークラスで module をインクルードしていたらスキップする */
        for (p = RCLASS(klass)->super; p; p = RCLASS(p)->super) {
            if (BUILTIN_TYPE(p) == T_ICLASS &&
                RCLASS(p)->m_tbl == RCLASS(module)->m_tbl) {
                if (RCLASS(module)->super) {
                    rb_include_module(p, RCLASS(module)->super);
                }
                if (changed) rb_clear_cache();
                return;
            }
        }
/*A*/   RCLASS(klass)->super = include_class_new(module, RCLASS(klass)->super);
/*B*/   klass = RCLASS(klass)->super;
        module = RCLASS(module)->super;
        changed = 1;
    }
    if (changed) rb_clear_cache();
}

(class.c)

最初の for 文がやっていることはコメントに書いてあるので、まずは 読みとばそう。その次を読むと、どうやらインクルード対象のクラスのスーパークラスを 差し替えているようである(A)。さらにそのあとは、差し替えたばかりの クラスと modulesuper に対して繰り返しているようだ。

modulesuper に入っているのは、module にインクルードした モジュールだろう(勘)。ということは B の部分はただのループ カウンタのインクリメントにすぎないので、Ainclude_class_new() が 重要なようだ。


static VALUE
include_class_new(module, super)
    VALUE module, super;
{
    NEWOBJ(klass, struct RClass);                 /* A */
    OBJSETUP(klass, rb_cClass, T_ICLASS);

    if (!RCLASS(module)->iv_tbl) {                /* B */
        RCLASS(module)->iv_tbl = st_init_numtable();
    }
    klass->iv_tbl = RCLASS(module)->iv_tbl;
    klass->m_tbl = RCLASS(module)->m_tbl;
    klass->super = super;                          /* C */
    if (TYPE(module) == T_ICLASS) {                /* D */
        RBASIC(klass)->klass = RBASIC(module)->klass;
    }
    else {
        RBASIC(klass)->klass = module;
    }
    OBJ_INFECT(klass, module);
    OBJ_INFECT(klass, super);

    return (VALUE)klass;
}

まず新しいクラスを作り(A)、続いて module のインスタンス変数 テーブル・メソッドテーブルをそれに移植(B)、スーパークラスを これまでのスーパークラス(super)にする(C)。つまり、module の 「化身」になるクラスを作って返すようだ。 また A のところでは構造体フラグを T_ICLASS というのにしており、 これが化身クラスの印らしい。ICLASS の Iinclude だろうか?

さらに D は、ちょっとわかりにくいのだが、rb_include_module に あった while ループの二回目以降に対処している。一回目は module は T_ICLASS ではない(T_MODULE だ)ので RBASIC(klass)->klass には その実体のモジュールがセットされる(if の偽側)。二回目以降は module は最初の module にインクルードされているモジュールだから、 それはすでに T_ICLASS 化されているはずで、その RBASIC(klass)->klass には 実体がセットされているはずである。なのでそれを取り出して使う(if の真側)。

具体例で見よう。まず m1m2 をインクルードすると……


im2 ---- m2
 |
m1

こうなるだろう。ここに、以下のようなクラス c1 を持ってくる。


parent    im2 ---- m2
 |         |
c1        m1

c1m1 をインクルードしよう。 まず一回目のループ (module = m1) で以下のように変わり、


parent   im2 ---- m2
 |        |
im1 ---- m1
 |
c1                  

二回目のループ (module = im2) でこうなる。


parent
 |
im2' ---------------------- m2
 |                 im2 ----
 |                  |
im1 -------------- m1
 |
c1

さてここまでくると rb_include_module() の読み飛ばしていた部分が 理解できる。


/* 既にスーパークラスで module をインクルードしていたらスキップする */
for (p = RCLASS(klass)->super; p; p = RCLASS(p)->super) {
    if (BUILTIN_TYPE(p) == T_ICLASS &&
        RCLASS(p)->m_tbl == RCLASS(module)->m_tbl) {
        if (RCLASS(module)->super) {
            rb_include_module(p, RCLASS(module)->super);
        }
        if (changed) rb_clear_cache();
        return;
    }
}

親(p)のうち T_ICLASS で(つまりモジュールで)同じメソッドテーブルを 持つものがあったらそれは同じモジュールを二回インクルードしたという ことなのでスキップする。ただしそのモジュールがインクルードしている モジュールがあったらそっちも再確認する、というわけだ。

p はすでにインクルードしたモジュールなのだから、それにインクルード されたモジュールもすでにインクルードしているはず……と一瞬思ったが、 以下のような状況がありうる。


module M1; end
module M2; end
class C
  include M2   # まだ M1 はインクルードされてない
end

module M2
  include M1
end
class C
  include M2   # 今度はインクルードされている!
end

逆に言うと、include は「リアルタイム」でない。つまり、include した 結果が即座に反映しないことがある。

また以上のような仕組みなので、モジュールの特異メソッドはインクルード 先クラスオブジェクト(またはモジュール)に継承しない。継承させるには 特異クラスもいっしょにつなぎかえないといけないが、それはやっていな かったからだ。というわけで特異メソッドを定義したいときは Module#append_features をオーバーライドするのが常套手段だ。

もうひとつ考えることがある。どうやら RBasic(iclass)->klass は単に 実体のモジュールを示すためだけに使われているようなので、ICLASS に 対してメソッドが呼ばれたりする状況は非常にまずい。だから ICLASS は絶対にスクリプトから見えてはならないはずである。そして実際 Object#type など全てのメソッドは ICLASS をスキップする。特異クラス の場合のような例外的な手段もない。

Copyright (c) 1998-2002 Minero Aoki <aamine@loveruby.net>
This site is link free.