メソッド

メソッドの姿

Ruby メソッドの保持方法

C で書いたメソッドと Ruby で書いたメソッド

ようするに構文木がハッシュに入ってる

alias と undef

メソッドは前節のようなつくりで登録され、動的に変更が可能であるから alias や undef はかなり簡単に実装できそう…なのだが、実はそうでもない。 例えば、以下のようなスクリプトを実行してみると、

def a
  raise
end
alias b a
b
例外が発生したメソッド名としては 'a' が表示される。b という名前で呼んだのに 表示は a なのだ。alias はこのようなことができるように実行されなければいけない。 それでは、ソースを見ていこう。alias の本体は rb_alias() である。

メソッドの探索

まずはメソッド呼び出しの手順を簡単に見てみよう。

  1. レシーバオブジェクトの構造体からそのクラスを取りだす
  2. クラスオブジェクトの構造体の m_tbl からメソッド本体を取りだす
  3. もし見つからなかったらさらに super のクラスから探す
  4. 見付かったらメソッド実行
このメソッド検索の部分を実際にやっているのが search_method() だ。
static NODE*
search_method(klass, id, origin)
    VALUE klass, *origin;
    ID id;
{
    NODE *body;

    while (!st_lookup(RCLASS(klass)->m_tbl, id, &body)) {
	klass = RCLASS(klass)->super;
	if (!klass) return 0;
    }

    if (origin) *origin = klass;
    return body;
}

(eval.c)

メソッド探索の原理は以上の通りなのだが、実際に実行する段になると、 メソッド呼び出しのたびにハッシュを引いて検索するのはちょっと遅いような 気がする。これを改善するため Ruby では一回呼出したメソッドはキャッシュ されるようになっている。

メソッドキャッシュの実装

では、このキャッシュの実装 (eval.c) も見ておこう。まずキャッシュエントリの 配列をファイル static に宣言しておく。エントリのサイズは CACHE_SIZE=0x800 (2048) である。メソッドはクラスとメソッド名だけで一意に決まるので、この二つから一意の 配列のインデックス (0x000-0x7ff) を生成する計算式を作れば一回の計算でめざす メソッドにたどりつけるはずである。この式は現在は EXPR1 すなわち (((c)<<3)^(m))&0x7ff となっている。c がクラス(の VALUE の値)で、 m がメソッド名 (の ID) だ。このキャッシュの構造は、一回呼ばれたメソッドは すぐにまた呼びだされることが多いという経験上の事実に基いている。

ただしこのインデクシングは完全ハッシュでも なんでもないので、違うメソッドが偶然同じインデックスを生成して上書きされて しまうこともありうる。だがこれはあくまでキャッシュなので上書きされてしまっても 問題はないのだ。ただ動作が少し遅くなるだけである。

キャッシュまでを含めた呼び出しは rb_call (eval.c) で、キャッシュを含めた 探索は rb_get_method_body (eval.c)で実装されている。どちらも素直な構造 なのでとくに引用はしない。自分で読んでみてほしい。

可視性

御存知のとおり、Ruby のメソッドには「呼び出してもいいひと」と 「いけないひと」の属性がある。どっからでも呼べるのが public、自分からしか 呼べないのが private、同じクラスまたはそのサブクラスのインスタンスからしか 呼べないのが protected、であった。静的な言語だとコンパイルの時点でそれを 呼べるか呼べないかが決まってしまうのだが、Ruby では実際にメソッドを呼ぶ 時になって始めて検査する。

メソッド起動

rb_eval() メソッド呼びだしのエントリポイント NODE_CALL NODE_FCALL NODE_VCALL