Ruby歴が1年くらいのエンジニアです!
最近ようやくRubyがどういう風に動いてるのかに興味が出てきたので、Rubyのコア部分のコードを頑張って読んでみる事にしました。
最終的には、コミット出来るところまでいけたら最高だなと思っています。(不定期更新です)
準備
現在の安定版は 2.3.1のようなので、2.3.1で勉強していきたいと思います。
ソースコードのダウンロードはこちらから
https://www.ruby-lang.org/ja/downloads/
Documentはこちらを参考にしたり、ググったりです
http://docs.ruby-lang.org/ja/2.3.0/class/Object.html
第一印象
意気揚々とダウンロードして読み始めたのですが
最近ようやっとrubyのコアの部分のコードをちょっとずつ見てるんだけど、さっぱりわからん笑
— takuji (@cat__tac) 2016年5月11日
どこから読み始めたらいいんや笑
という感じです笑
なので、本当にのんびりな進みになると思うので、生温い目で見守っていてください笑
足がかり
辛さを感じながら、Cのソースコードを読んで行くと
rb_define_method
という、いかにもmethodを定義してそうなコードを見つけました。
これだ!と想い、git grep "rb_define_method"
で検索してみると、出てくる出てくる!
見た事ある単語も出てきて、だいぶ安心しました笑
webで検索しても出てきました。
http://rurema.clear-code.com/query:rb_define_method/version:2.3.0/
ただしくは
void rb_define_method(VALUE klass, const char *name, VALUE(*func)(), int argc)
が正しいらしいです。
- klassがメソッドが所属するクラス
- *nameが、インスタンスメソッドの名前
- *funcが、どんな挙動をするか
- argcが、メソッドの引数の数
のようです!
これがわかったところで簡単そうなメソッドを読んでみます
Numeric.cのfix_plusについて読んでみる
今回は以下のメソッドについて深めて行きたいと思います。
rb_define_method(rb_cFixnum, "+", fix_plus, 1);
まず、numeric.c内でrb_cFixnumで検索をかけるとたくさん出てきます。
で、これを見てみると、
rb_cFixnum = rb_define_class("Fixnum", rb_cInteger);
というまたまたいかにも、クラスを定義してそうなコードを発見しました。
ここに書かれている、rb_cIntegerを検索すると
rb_cInteger = rb_define_class("Integer", rb_cNumeric);
が出てきました。
どんどん、クラスが継承されていっているみたいです。
さらに、掘って行きます。
rb_cNumeric = rb_define_class("Numeric", rb_cObject);
が出てきました。
で、rb_cObjectを調べるとobject.cファイルの3378行目にありました。
rb_cObject = rb_define_class("Object", rb_cBasicObject);
で、rb_cBasicObjectを調べると
rb_cBasicObject = rb_define_class("BasicObject", Qnil);
Qnilってなんだ!!
と思ったら、
ここのWEBサイトにかいてありました。
http://ruby.gfd-dennou.org/tutorial/ruby-ext/
Rubyから直接呼び出す関数は戻り値としてRubyのオブジェクトを返す 必要があります。 今は特に何も戻り値が必要でないので、Rubyの「nil」に 対応する「Qnil」という定数を与えています。
というわけで、rb_cBasicObjectが一番元のクラスだという事が判明しました。
階層的には
rb_cBasicObject
↓
rb_cObject
↓
rb_cNumeric
↓
rb_cInteger
↓
rb_cFixnum
という、継承構造のようです。
本題のfix_plusの方を調べて行きたいと思います。
rb_define_method(rb_cFixnum, "+", fix_plus, 1);
rb_cFixnumというクラスに、+という名前で、fix_plusの挙動を定義します。で、引数はひとつです。
では、fix_plusの挙動について調べましょう。
static VALUE
fix_plus(VALUE x, VALUE y)
{
if (FIXNUM_P(y)) {
long a, b, c;
VALUE r;
a = FIX2LONG(x);
b = FIX2LONG(y);
c = a + b;
r = LONG2NUM(c);
return r;
}
else if (RB_TYPE_P(y, T_BIGNUM)) {
return rb_big_plus(y, x);
}
else if (RB_TYPE_P(y, T_FLOAT)) {
return DBL2NUM((double)FIX2LONG(x) + RFLOAT_VALUE(y));
}
else if (RB_TYPE_P(y, T_COMPLEX)) {
VALUE rb_nucomp_add(VALUE, VALUE);
return rb_nucomp_add(y, x);
}
else {
return rb_num_coerce_bin(x, y, '+');
}
}
引数2つじゃね?とおもったのですが、0が1で、1が2的なあれかなと、とりあえず思って今は流しておきたいと思います。判明したら、正しく書きます。
+だから簡単かなと思ったら、意外とたくさん条件分岐があって面食らっています笑
FIXNUM_P(y)とは!?
検索したらでてきました。関数の用です(version違うけどきっと同じだと思われます。)
http://docs.ruby-lang.org/ja/1.9.3/function/FIXNUM_P.html
というわけで、yがFIXNUMだとtrueが返るようです。
yがFIXNUMの場合
FIX2LONG
この関数は見た目からして、型をFIXNUMをLONGにするやつっぽいのでそういう認識で活きます。
LONG2NUM
この関数も、型をLONGからNUMに返る奴でしょう。
まとめ
このメソッドは
rb_define_method(rb_cFixnum, "+", fix_plus, 1)
Fixnumというクラスに対して、+が呼ばれた時に
引数yがFIXNUMの時、
引数xと引数yをまず、LONG型に変換し、足し算してから、返り値として、NUM型のrを返すというメソッドを定義する
という、もののようです。
実際に読んでみると意外なんとかなるなという印象です。(これであっていれば笑)
きっと難しいところはめっちゃ難しいんだと思いますがちょっとずつ頑張って行こうと思います。