Rubyの#13053を読んでみる(2)
前回に続いてrb_ary_select_bang()を読んでいきます。 忘れた方も多いと思いますがArray#select!のことです。
rb_ary_select_bang()を読む
static VALUE rb_ary_select_bang(VALUE ary) { struct select_bang_arg args; RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length); rb_ary_modify(ary); args.ary = ary; args.len[0] = args.len[1] = 0; return rb_ensure(select_bang_i, (VALUE)&args, select_bang_ensure, (VALUE)&args); }
これ、まず驚くのはごく短い実装だということ。謎の処理がいくつかありますが、ここでは全て無視して最後のreturn rb_ensure()
に着目します。どうやらここでselect_bang_ensure関数へのポインタをrb_ensure()
に渡していることが分かります。
rb_ensure()とは?
rb_ensureを調べます。どうやらarray.cの中には定義はないようです。ではどこにあるのでしょう?doc/extension.rdoc
またはdoc/extension.ja.rdoc
に答えがありました。
VALUE rb_ensure(VALUE (*func1)(ANYARGS), VALUE arg1, VALUE (*func2)(ANYARGS), VALUE arg2) 関数func1をarg1を引数として実行し, 実行終了後(たとえ例外が 発生しても) func2をarg2を引数として実行する. 戻り値はfunc1 の戻り値である(例外が発生した時は戻らない).
(´・ω・`)え?つまりselect_bang_i(&args)
を実行してから、select_bang_ensure(&args)
するの?なんでそんなことするの?
5分程悩みましたが、説明から察するに、例外発生時でも実行される処理を書きたかったのですかね。begin rescure ensure
みたいな。
だからrb_ensureなのか!
この実装はeval.cにありました。
VALUE rb_ensure(VALUE (*b_proc)(ANYARGS), VALUE data1, VALUE (*e_proc)(ANYARGS), VALUE data2) { /* snip */ if ((state = EXEC_TAG()) == 0) { result = (*b_proc) (data1); // b_proc実行 } /* snip */ (*ensure_list.entry.e_proc)(ensure_list.entry.data2); // e_proc実行 /* snip */ return result; }
オッケー。 関数ポインタのはずの第1引数と第3引数が、ANYARGSという謎マクロとなっているので一旦置いとこう(;´Д`) ですが例外処理のensureで間違いなさそうです。
selecl_bang_i()を読む
気を取り直して、select_bang_i
を読むことで何か掴めるかもしれないので読んでみます。#13053読むためには追わなくて良い気がしますが、OSS読むときってこういうyac shavingが捗りますよね・・。
array.cに定義されています。
static VALUE select_bang_i(VALUE a) { volatile struct select_bang_arg *arg = (void *)a; VALUE ary = arg->ary; long i1, i2; for (i1 = i2 = 0; i1 < RARRAY_LEN(ary); arg->len[0] = ++i1) { // i1, i2を0からary.lengthまで。 VALUE v = RARRAY_AREF(ary, i1); if (!RTEST(rb_yield(v))) continue; if (i1 != i2) { rb_ary_store(ary, i2, v); } arg->len[1] = ++i2; } return (i1 == i2) ? Qnil : ary; }
なんだかいよいよアルゴリズム的な処理が出てきました。
関数名からselect!
関連であることは間違いないのでしょうが、難しそうです。
新しいマクロRARRAY_AREF、RTEST、他関数が出てきたので先に見ます。
- まずRARRAY_AREF、これはaryからi1番目の値を取得するマクロのようです。
- 次にRTEST、これはrubyっぽい真偽判定マクロのようです。つまりnilとfalse以外は真、0も真です。
- そしてrb_yieldは、yieldのC実装でvはブロックの引数。
- 最後にrb_ary_store(ary, i2, v)ですが、
ary[i2] = v
ということみたいです。
ここまで調べて気づきましたが、下docs.ruby-lang.org*1がリファレンスとして優秀です。 さらに脱線しますが、Ruby Arrayの略でRARRAYみたいですね。ラレーって読んでました(;´Д`) CRubyでは、Rとかrとかrbっていうprefixをつける流儀みたいです。
さて、この処理のi1とi2を何に使っているか読んでいきます。説明のために次のrubyコードを考えてみます。
array = [3, 1, 5, 4, 2] array.select! {|v| v.even? } p array #=> [4, 2]
arrayのうち偶数でない物を取り除く処理です。 それではselect_bang_i()をみていきます。RARRAY_LEN(ary)は5です。i1は0..4の5周イテレートされます。
1週目だけ見てみる
[3, 1, 5, 4, 2].select!
を実行したとすると、一週目はi1=0, i2=0です。
v = RARRAY_AREF(ary, i1)
によって最初のブロック(というか値)の3へのアドレスが渡されます。
rb_yield(&3)
によってブロックが実行されます。待ってブロックはどこだ(;´Д)?
ary.select! {|v| }っていう感じに、意味ない事になってないですか?どこにいつブロックの処理
v.even?`が渡されたか不明です。
これは宿題に・・・。
仕方ないので、ここでrubyのコードを振り返ってみます。
なんとなくですが、if (!RTEST(rb_yield(v))) continue;
でcontinueするのは、条件に合わないときと予想します。
rb_ary_store()されるのは条件にあったとき、すなわち[2, 4]が残る。となれば予想はselect!の処理に合致しそうですね。
そしてarg->len[1]
というのが、新しいaryの長さ。すなわち2となり、len[0]は元の配列長さ5になるので、辻褄が合いました。
まとめ
arg->len[0]とarg->len[1]が代入されているのはselect_bang_i()で、 それぞれ、len[0]は元の配列の長さ、len[1]はselect!後の配列の長さということが分かりました。 変数名から分かりにくいですね・・。
lenの正体が分かったところで、次回select_bang_ensure()に戻って処理を追っていきたいと思います。
スポンサーリンク
- 作者: Rubyサポーターズ,すがわらまさのり,寺田玄太郎,三村益隆,近藤宇智朗,橋立友宏,関口亮一
- 出版社/メーカー: 技術評論社
- 発売日: 2013/08/10
- メディア: 大型本
- この商品を含むブログ (22件) を見る