ruby/ruby にパッチ送ろうとしたけど失敗してる話 part2
注: part2といいつつpart1は書き上がってないのでこれが最初です
背景
るりまのサンプルコードを整備しようというプロジェクトがあるのはご存知ですか。
id:tbpg さんがこちらで精力的に活動されているのですが、ある日こういう話を聞きました。
「Enumerator::Lazy#chunk
の定義が可変長引数に見えるんだけど実際は引数渡すとエラーになるんだよね〜」
で、サンプルを書こうとしたときにちょっと詰まったと聞いたので、定義を可変長引数でなくできないだろうかと Ruby hack Challenge もくもく会#2 のネタにさせてもらいました。
事前調査
まずはざっと定義をみておきましょう。ruby/rubyにおいて組込みで用意されているクラスのメソッドはC言語で書かれていることが多く、その場合は rb_define_method
を使ってメソッドが定義されます1。
使い方は以下のような感じです。
rb_define_method(rb_cKlass, "method_name", cfunk, argc)
rb_cKlass
クラスに "method_name"
メソッドを宣言しており、実体が cfunk
、その時の引数の数が argc
です。覚えてしまえばかんたんですね。
さて、今回は chunk
メソッドを探すので "chunk"
で検索すればよさそうです。
$ git grep '"chunk"' enum.c: rb_define_method(rb_mEnumerable, "chunk", enum_chunk, 0); enumerator.c: rb_define_method(rb_cLazy, "chunk", lazy_super, -1);
2件ヒットしましたね。ruby/rubyのことは全然わかりませんが幸い少しはRubyの知識があるので rb_mEnumerable
は Enumerable
モジュール、 rb_cLazy
は Enumerator::Lazy
クラスだろうと推測できます。
一応それぞれの宣言を見ておきましょう。まずは Enumerator::Lazy
のほうから。
/* enumerator.c */ void InitVM_Enumerator(void) { ... rb_cEnumerator = rb_define_class("Enumerator", rb_cObject); rb_include_module(rb_cEnumerator, rb_mEnumerable); ... /* Lazy */ rb_cLazy = rb_define_class_under(rb_cEnumerator, "Lazy", rb_cEnumerator); ... rb_define_method(rb_cLazy, "chunk", lazy_super, -1); ... }
void Init...(void)
という関数はRubyを実行したときに、Rubyコードを実行する前の初期化をする関数です(だと思っています)。
rb_define_class_under
は以下のシグネチャをもちます。名前的に outer
がネームスペース、super
が親クラスでしょう。
VALUE rb_define_class_under(VALUE outer, const char *name, VALUE super)
よって rb_cLazy
は rb_cEnumerator
のインナークラスであり、なおかつ rb_cEnumerator
の子クラスであることがわかります。Rubyでかくとこんな感じでしょうか。
class Enumerator class Lazy < Enumerator end end
関数の実体は lazy_super
で argc が -1なので可変長です。lazy_super
の定義も参照しましょう。
/* enumerator.c */ static VALUE lazy_super(int argc, VALUE *argv, VALUE lazy) { return enumerable_lazy(rb_call_super(argc, argv)); }
enumerable_lazy
は、まあたぶん lazy
化してくれるんでしょう。 rb_call_super
は、まあsuper呼んでそうな気がしますね。こういうのは深追いすると時間が溶けていくので一旦はここで放置します。
rb_call_super
で呼ばれる関数を一応確認しましょう。先程確認したように rb_cLazy
の親クラスは rb_cEnumerator
ですのでそちらの定義を確認します。
抜粋した中に rb_include_module(rb_cEnumerator, rb_mEnumerable)
という箇所があり rb_mEnumerable
が include されていることが確認できさらに grep 結果から実体が enum.c
の enum_chunk
であることそしてargcが0であり引数をとらないことがわかります(早口
enum.c: rb_define_method(rb_mEnumerable, "chunk", enum_chunk, 0);
enum_chunk
まで追う必要はなさそうですがシグネチャくらいは置いておきます。
static VALUE
enum_chunk(VALUE enumerable)
{
...
}
ここまでで、以下のことが確認できました。
Enumurator::Lazy#chunk
が可変長引数であること- 実体は
Enumerator#chunk
といってよさそうなこと Enumerator#chunk
は引数をとらないこと
hackしてみる
さて、では引数の数を変えてみましょう。さきほど見たように rb_define_method
の argc を -1 から 0 に変えればよいでしょう。かんたんですね。こんなんで「hackしてみる」というような見出しをつけていいものでしょうか。心が痛みます(フラグ
diff --git a/enumerator.c b/enumerator.c index d61d79e897..da109c8116 100644 --- a/enumerator.c +++ b/enumerator.c @@ -2379,7 +2379,7 @@ InitVM_Enumerator(void) rb_define_method(rb_cLazy, "drop", lazy_drop, 1); rb_define_method(rb_cLazy, "drop_while", lazy_drop_while, 0); rb_define_method(rb_cLazy, "lazy", lazy_lazy, 0); - rb_define_method(rb_cLazy, "chunk", lazy_super, -1); + rb_define_method(rb_cLazy, "chunk", lazy_super, 0); rb_define_method(rb_cLazy, "slice_before", lazy_super, -1); rb_define_method(rb_cLazy, "slice_after", lazy_super, -1); rb_define_method(rb_cLazy, "slice_when", lazy_super, -1);
さっそくビルドして検証しましょう。こういうgemが絡まないような検証のときは miniruby でやるものだと聞いた気がするのでそうします。検証コードも、どうせ動くし雑でいいんじゃないでしょうか(フラグ2
# tmp/lazy.rb arr = %w(a a b) p Enumerator.instance_method(:chunk).arity p Enumerator::Lazy.instance_method(:chunk).arity p arr.chunk do |e| p e end p arr.lazy.chunk do |e| p e end
さて、minirubyを作って実行しましょう。
$ make miniruby $ ruby -v ruby 2.4.2p198 (2017-09-14 revision 59899) [x86_64-darwin16] $ ruby tmp/lazy.rb 0 -1 #<Enumerator: ["a", "a", "b"]:chunk> #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: ["a", "a", "b"]>:chunk>> $ ./miniruby tmp/lazy.rb 0 0 #<Enumerator: ["a", "a", "b"]:chunk> Traceback (most recent call last): 2: from tmp/lazy.rb:8:in `<main>' 1: from tmp/lazy.rb:8:in `chunk' tmp/lazy.rb:8:in `chunk': wrong number of arguments (given 1309040872, expected 0) (ArgumentError)
ん……?
given 1309040872
( д) ゚ ゚
引数の数が合わず、 expected が 0 なのに 1309040872 もの引数が渡されたことになっています。んなわけあるかーい。
なんどか実行してみると given の値が変化していることがわかります。また、この大きさからだいたいメモリアドレスを指しているんじゃないかなあという気持ちになります。
関数の中身をモックにしたりして原因を切り分けてみてはどうだろう、ともくもく会参加者の yui-knk さんにアドバイスをもらったのでやってみましょう。
diff --git a/enumerator.c b/enumerator.c index d61d79e897..381f039c8a 100644 --- a/enumerator.c +++ b/enumerator.c @@ -2270,6 +2270,7 @@ lazy_uniq(VALUE obj) static VALUE lazy_super(int argc, VALUE *argv, VALUE lazy) { + return INT2FIX(1); return enumerable_lazy(rb_call_super(argc, argv)); } @@ -2379,7 +2380,7 @@ InitVM_Enumerator(void) rb_define_method(rb_cLazy, "drop", lazy_drop, 1); rb_define_method(rb_cLazy, "drop_while", lazy_drop_while, 0); rb_define_method(rb_cLazy, "lazy", lazy_lazy, 0); - rb_define_method(rb_cLazy, "chunk", lazy_super, -1); + rb_define_method(rb_cLazy, "chunk", lazy_super, 0); rb_define_method(rb_cLazy, "slice_before", lazy_super, -1); rb_define_method(rb_cLazy, "slice_after", lazy_super, -1); rb_define_method(rb_cLazy, "slice_when", lazy_super, -1);
INT2FIX
はC言語での int を Ruby の世界の Fixnum に変換するマクロです。必ず1を返すようにしてみました。
$ make miniruby $ ./miniruby tmp/lazy.rb 0 0 #<Enumerator: ["a", "a", "b"]:chunk> 1
これでは普通に通りますね。lazy_super
の中身は、 enumerable_lazy
と rb_call_super
にわけられるのでとりあえず後者だけを呼ぶようにしてみます。
diff --git a/enumerator.c b/enumerator.c index d61d79e897..d022c09ebd 100644 --- a/enumerator.c +++ b/enumerator.c @@ -2270,6 +2270,7 @@ lazy_uniq(VALUE obj) static VALUE lazy_super(int argc, VALUE *argv, VALUE lazy) { + return rb_call_super(argc, argv); return enumerable_lazy(rb_call_super(argc, argv)); } @@ -2379,7 +2380,7 @@ InitVM_Enumerator(void) rb_define_method(rb_cLazy, "drop", lazy_drop, 1); rb_define_method(rb_cLazy, "drop_while", lazy_drop_while, 0); rb_define_method(rb_cLazy, "lazy", lazy_lazy, 0); - rb_define_method(rb_cLazy, "chunk", lazy_super, -1); + rb_define_method(rb_cLazy, "chunk", lazy_super, 0); rb_define_method(rb_cLazy, "slice_before", lazy_super, -1); rb_define_method(rb_cLazy, "slice_after", lazy_super, -1); rb_define_method(rb_cLazy, "slice_when", lazy_super, -1);
実行してみると……
$ ./miniruby tmp/lazy.rb 0 0 #<Enumerator: ["a", "a", "b"]:chunk> Traceback (most recent call last): 2: from tmp/lazy.rb:8:in `<main>' 1: from tmp/lazy.rb:8:in `chunk' tmp/lazy.rb:8:in `chunk': wrong number of arguments (given -477732640, expected 0) (ArgumentError)
同じ事象が出ました。どうもsuper側で何かが起きているようですね。
super側の定義をタグジャンプして追ってみたのですがよくわかりません。仕方がないのでlldb(デバッガ)で無理やり止めてみるとあっさりわかりました2。
ブレークポイントを挟むのですが、 vm_call_super
に挟むといろんなものでとまってめんどくさそうです。mid かなんかでとめるとよさそうな気がしました。rb_intern
で文字列から ID
3 を取り出せるのでif文つくっておけばいいでしょう。
diff --git a/enumerator.c b/enumerator.c index d61d79e897..da109c8116 100644 --- a/enumerator.c +++ b/enumerator.c @@ -2379,7 +2379,7 @@ InitVM_Enumerator(void) rb_define_method(rb_cLazy, "drop", lazy_drop, 1); rb_define_method(rb_cLazy, "drop_while", lazy_drop_while, 0); rb_define_method(rb_cLazy, "lazy", lazy_lazy, 0); - rb_define_method(rb_cLazy, "chunk", lazy_super, -1); + rb_define_method(rb_cLazy, "chunk", lazy_super, 0); rb_define_method(rb_cLazy, "slice_before", lazy_super, -1); rb_define_method(rb_cLazy, "slice_after", lazy_super, -1); rb_define_method(rb_cLazy, "slice_when", lazy_super, -1); diff --git a/vm_eval.c b/vm_eval.c index f0f336a233..a2efe31736 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -229,6 +229,9 @@ vm_call_super(rb_execution_context_t *ec, int argc, const VALUE *argv) return method_missing(recv, id, argc, argv, MISSING_SUPER); } else { + if (id == rb_intern("chunk")) { + printf("break\n"); + } return vm_call0(ec, recv, id, argc, argv, me); } }
lldb でブレークポイントを貼って実行します。b
でブレークポイントを貼って run ARGS
で実行。とまったあとは p
で中身見て bt
でトレースが見れます。n
がnext, s
が step, c
がcontinueなのまで覚えておけばだいたい大丈夫でしょう。
$ make miniruby $ lldb ./miniruby (lldb) target create "./miniruby" Current executable set to './miniruby' (x86_64). (lldb) b vm_eval.c:233 Breakpoint 1: where = miniruby`vm_call_super + 251 at vm_eval.c:233, address = 0x0000000100272a0b (lldb) run tmp/lazy.rb Process 35984 launched: './miniruby' (x86_64) 0 0 #<Enumerator: ["a", "a", "b"]:chunk> Process 35984 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 frame #0: 0x0000000100272a0b miniruby`vm_call_super(ec=0x0000000100606e78, argc=25583824, argv=0x00000001018660d0) at vm_eval.c:233 230 } 231 else { 232 if (id == rb_intern("chunk")) { -> 233 printf("break\n"); 234 } 235 return vm_call0(ec, recv, id, argc, argv, me); 236 } Target 0: (miniruby) stopped. (lldb) bt * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 * frame #0: 0x0000000100272a0b miniruby`vm_call_super(ec=0x0000000100606e78, argc=25583824, argv=0x00000001018660d0) at vm_eval.c:233 frame #1: 0x00000001002728a1 miniruby`rb_call_super(argc=25583824, argv=0x00000001018660d0) at vm_eval.c:244 frame #2: 0x000000010008c60f miniruby`lazy_super(argc=25583824, argv=0x00000001018660d0, lazy=0) at enumerator.c:2273 frame #3: 0x00000001002844d3 miniruby`call_cfunc_0(func=(miniruby`lazy_super at enumerator.c:2272), recv=4320551120, argc=0, argv=0x0000000102000048) at vm_insnhelper.c:1735 frame #4: 0x000000010028329d miniruby`vm_call_cfunc_with_frame(ec=0x0000000100606e78, reg_cfp=0x00000001020fffa0, calling=0x00007ffeefbfe428, ci=0x000000010051c910, cc=0x000000010051ca98) at vm_insnhelper.c:1924 frame #5: 0x000000010027e9bd miniruby`vm_call_cfunc(ec=0x0000000100606e78, reg_cfp=0x00000001020fffa0, calling=0x00007ffeefbfe428, ci=0x000000010051c910, cc=0x000000010051ca98) at vm_insnhelper.c:1940 frame #6: 0x000000010027de9e miniruby`vm_call_method_each_type(ec=0x0000000100606e78, cfp=0x00000001020fffa0, calling=0x00007ffeefbfe428, ci=0x000000010051c910, cc=0x000000010051ca98) at vm_insnhelper.c:2238 frame #7: 0x000000010027dc30 miniruby`vm_call_method(ec=0x0000000100606e78, cfp=0x00000001020fffa0, calling=0x00007ffeefbfe428, ci=0x000000010051c910, cc=0x000000010051ca98) at vm_insnhelper.c:2359 frame #8: 0x000000010027db85 miniruby`vm_call_general(ec=0x0000000100606e78, reg_cfp=0x00000001020fffa0, calling=0x00007ffeefbfe428, ci=0x000000010051c910, cc=0x000000010051ca98) at vm_insnhelper.c:2402 frame #9: 0x000000010026a5d8 miniruby`vm_exec_core(ec=0x0000000100606e78, initial=0) at insns.def:933 frame #10: 0x0000000100279586 miniruby`vm_exec(ec=0x0000000100606e78) at vm.c:1797 frame #11: 0x000000010027a2fb miniruby`rb_iseq_eval_main(iseq=0x0000000101866dc8) at vm.c:2045 frame #12: 0x000000010009b928 miniruby`ruby_exec_internal(n=0x0000000101866dc8) at eval.c:246 frame #13: 0x000000010009b831 miniruby`ruby_exec_node(n=0x0000000101866dc8) at eval.c:310 frame #14: 0x000000010009b7f0 miniruby`ruby_run_node(n=0x0000000101866dc8) at eval.c:302 frame #15: 0x0000000100001881 miniruby`main(argc=2, argv=0x00007ffeefbfed10) at main.c:42 frame #16: 0x00007fff5bd3b145 libdyld.dylib`start + 1
長いのが出てきてウゲーとなりますが、よくみるとargcという文字列も見えますね。そこだけ追うと以下の箇所がめちゃくちゃあやしいです。
frame #2: 0x000000010008c60f miniruby`lazy_super(argc=25583824, argv=0x00000001018660d0, lazy=0) at enumerator.c:2273 frame #3: 0x00000001002844d3 miniruby`call_cfunc_0(func=(miniruby`lazy_super at enumerator.c:2272), recv=4320551120, argc=0, argv=0x0000000102000048) at vm_insnhelper.c:1735
call_cfunc_0
では 0 だった argc が lazy_super
には 25583824 で渡されています。こいつっぽい。
vm_insnhelper.c:1735 らしいので call_cfunc_0
を見てみましょう。
static VALUE call_cfunc_0(VALUE (*func)(ANYARGS), VALUE recv, int argc, const VALUE *argv) { return (*func)(recv); }
な、なんもわからん……。もう1つ前のフレームを見ましょう。vm_call_cfunc_with_frame
で vm_insnhelper.c:1924
だそうです。いらなそうなところを消していくとこんな感じです。
static VALUE vm_call_cfunc_with_frame(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc) { VALUE val; const rb_callable_method_entry_t *me = cc->me; const rb_method_cfunc_t *cfunc = vm_method_cfunc_entry(me); int len = cfunc->argc; VALUE recv = calling->recv; VALUE block_handler = calling->block_handler; int argc = calling->argc; ... val = (*cfunc->invoker)(cfunc->func, recv, argc, reg_cfp->sp + 1); ... return val; }
1924行目は val = ...
の行です。cfunc->invoker
が関数へのポインタになっていて4、invokerポインタの指す先の関数を cfunc->func, recv, argc, reg_cfp->sp + 1
という引数で呼んでいるようです。
どうして invoker を使っているのでしょうか。これは cfunc への呼び出しをラップするためのもののようです。例えばさっき書いた call_cfunc_0
もinvokerで、第一引数にCの関数をもらって呼び出しています。
Array
のメソッドを例にとると、引数が0個の rb_ary_uniq
のシグネチャは以下です。
static VALUE rb_ary_uniq(VALUE ary)
可変長引数の rb_ary_max
のシグネチャはこんな感じです。
static VALUE rb_ary_max(int argc, VALUE *argv, VALUE ary)
呼び出し方が全然違うので invoker でラップしているんだろう、という感じです。呼び出すときに呼び出し方を考えるより、定義したときに呼び出し方を決めておいたほうが楽だって話かと思いました。
さて、chunkのbacktraceに戻りましょう。invoker である call_cfunc_0 を再掲します。
static VALUE call_cfunc_0(VALUE (*func)(ANYARGS), VALUE recv, int argc, const VALUE *argv) { return (*func)(recv); }
このとき func は lazy_super
へのポインタなので lazy_super
を引数 recv
で呼び出しています。
static VALUE lazy_super(int argc, VALUE *argv, VALUE lazy) { return enumerable_lazy(rb_call_super(argc, argv)); }
lazy_superの第一引数はargcですね。recvはVALUEで実体はunsigned intです5。なのでargcを期待しているところにrecvを入れてしまったのが原因のようですね。lazy_super
に関しては recv
相当のものは第三引数になければなりません。
lazy_super
が可変長引数のシグネチャをもち、引数0個のシグネチャと異なるのにargcだけ変えたことが原因のようです。
別の関数を切って動かしてみましょう。
diff --git a/enumerator.c b/enumerator.c index da109c8116..31b18251b0 100644 --- a/enumerator.c +++ b/enumerator.c @@ -2273,6 +2273,12 @@ lazy_super(int argc, VALUE *argv, VALUE lazy) return enumerable_lazy(rb_call_super(argc, argv)); } +static VALUE +lazy_super_0(VALUE obj) +{ + return enumerable_lazy(rb_call_super(0, NULL)); +} + static VALUE lazy_lazy(VALUE obj) { @@ -2379,7 +2385,7 @@ InitVM_Enumerator(void) rb_define_method(rb_cLazy, "drop", lazy_drop, 1); rb_define_method(rb_cLazy, "drop_while", lazy_drop_while, 0); rb_define_method(rb_cLazy, "lazy", lazy_lazy, 0); - rb_define_method(rb_cLazy, "chunk", lazy_super, 0); + rb_define_method(rb_cLazy, "chunk", lazy_super_0, 0); rb_define_method(rb_cLazy, "slice_before", lazy_super, -1); rb_define_method(rb_cLazy, "slice_after", lazy_super, -1); rb_define_method(rb_cLazy, "slice_when", lazy_super, -1);
どうせargc, argvは使わないはずなのでsuperの呼び方は適当でいいでしょう。
$ make miniruby $ ./miniruby tmp/lazy.rb 0 0 #<Enumerator: ["a", "a", "b"]:chunk> #<Enumerator::Lazy: #<Enumerator::Lazy: #<Enumerator::Lazy: ["a", "a", "b"]>:chunk>>
無事通りました。
パッチを送るべきか?
よっしゃ、通った!パッチ送ろう!と思ったんですが。そもそも元のコードってRubyにおけるsuperへの委譲のよくあるパターンなんですよね。Rubyコードだとこんな感じ。
class Calc def add(x, y) x + y end end class Calc2 < Calc def add(*) # 可変長引数にしてsuperに委譲。superの引数の数を考えなくてよい super * 2 # 全部2倍にする、みたいな処理 end end
これは Calc#add
の引数の数がなんであろうと絶対2倍にするんだってときに役にたつんですよね。親クラスに依存しすぎないようにしてる。
一方今回いれたhackはこの Calc2#add
で仮引数が x, y
であると明示しているようなパターンになります。具体的には、こういうコードでの挙動が変わります。
# tmp/include.rb arr = %w(a a b) module Foo def chunk(arg) puts arg # override end end Enumerator::Lazy.include Foo arr.lazy.chunk(1) { |e| e }
まあこのコードが何がしたいのかはおいといて……。include によって Enumerator::Lazy#chunk
の super 呼び出しが Enumerator#chunk
から Foo#chunk
に変わっています。Foo#chunk
は明示的に引数を1つとるメソッドです。
$ ruby tmp/include.rb 1 $ ./miniruby tmp/include.rb Traceback (most recent call last): 1: from tmp/include.rb:9:in `<main>' tmp/include.rb:9:in `chunk': wrong number of arguments (given 1, expected 0) (ArgumentError)
今回のパッチがあたっている miniruby のほうでは Enumerator::Lazy#chunk
が委譲先の引数の数を気にするようになったことで ArgumentError になってしまいました6。
とゆーわけでそもそものアプローチが筋悪だったのかな、というところで今回はおしまい。いつになったらそれっぽいパッチが送れるようになるのでしょうか……。
MDNのドキュメントにコントリビュートしてみたのでやり方書くよ
MDN、ご存知ですか。jsとかcssとかでググってみるとだいたいひっかかると思います。
んで、これをちょっと読んでたんですがサンプルコードがわけわかんなかったんですよね。
僕が作った例のがイケてるんじゃね?と思ったのでコントリビュートしてみました。
やってみると結構かんたんだったので日本語でやったよってブログを書いたら、みんなもMDN書いてくれないかなー、ついでにブログのアクセス数増えないかなーみたいなお気持ちでこの記事を書いてます。
元ドキュメントのなにがわからんかったのか
arrow function は独自の arguments
を持たないっていうところ1の説明のサンプルが3つあってこんな感じでした。
var arguments = 42; var arr = () => arguments; arr(); // 42
レキシカルスコープ上の arguments 変数を見てる例。これはまあわかる
function foo() { var f = (i) => arguments[0] + i; // foo's implicit arguments binding return f(2); } foo(1); // 3
アロー関数で定義された関数 f の中では arguments は参照できず、
関数 foo の arguments がレキシカルに見えてて、
関数 f の arguments はとれてないぞという例。
なんだけど関数 f に仮引数名がついてるせいで
arguments で取りたいんだろうなという気持ちが推測しにくい……。
ここで「rest parameters を代わりに使えるよ」という説明2がはいる
function foo() { var f = (...args) => args[0]; return f(2); } foo(); // 2
この例がキツくて、 arguments の代わりに使えるって言ってるのにさっきのfooと全然違うことしてる。
というわけで一連の流れがよくないんですよね。
たとえばこんな感じの例だとどうだろうかーと書いてみたのが以下。
var arguments = [1, 2, 3]; var arr = () => arguments[0]; arr(); // 1 function foo(n) { var f = () => arguments[0] + n; // foo's implicit arguments binding. arguments[0] is n return f(10); } foo(1); // 2 /* ここに説明がはいる */ function foo(n) { var f = (...args) => args[0] + n; return f(10); } foo(1); // 11
id:alluser に確認してみたら、こっちのがいいんじゃないのとのこと。
というわけでこの差分を突っ込むぜー
MDNの記事の編集方法
じゃあどうやってなおすかという話なんですが割りとあっさりできました。
とりあえずはアカウント作らないとなので作成。
ページ右上にGitHubでログインできそうななにかがあるのでクリック
そうするとよくある2FAの画面がでてくるので、よく読んでOK。
僕がみたときはGitHubに登録してあるメールアドレス3へのアクセスをしてもいいかと聞かれました。
その後の登録フォームはちゃちゃっと埋めましょう。どのメールアドレス使うとか。
これでアカウント登録が完了です。
んで、次はここをやるぞーってページで編集ボタンをおします。
これも右上のほうにあります。
そうするとエディタ画面にとぶので、もうあとは直すだけです。
見たまま編集的な感じなので使い方でそんなに戸惑うことはないんじゃないかなー、ということで省略。
最後投稿する前にページ下部の「リビジョンのコメント」はわすれずに。
コミットログと同じなので丁寧に書いておきましょう4。
そうするとこんな感じ(リンク)に何を変更したよーみたいな履歴が残ります。
はい、かんたーん。
というわけで気になるところがあったらバンバン変更してみましょう。
参考
あとからみたら日本語でコントリビュートのやり方書いてあった
-
ここ Arrow functions - JavaScript | MDN だけどもう書き換えちゃった↩
-
In most cases, using rest parameters is a good alternative to using an arguments object.↩
-
public にしてないアドレスも含まれます。たぶん form に使うメールアドレスの候補として入れるのにしか使ってないと思うんだけど確証はないです。↩
-
これのちょっと上に「レビューを受ける」みたいなチェックボックスがあるんだけどこれチェックしても直後に反映されてビビった↩
Ruby hack Challenge もくもく会 に参加してきました
一言感想。コミッターの方が多数いてすげーなーという気持ちになりました
えーとこの会自体は Ruby hack Challenge という企画のもくもく会です。
企画自体はこちらのレポを参照するのがよいかと。
僕は rhc 自体には参加してないのですが RubyKaigi 以降はよく ruby/ruby を眺めているのでいってきました。
もくもく会なのでみんな適当になんかする、という感じだったのですが、僕はあまり何をするか考えていませんでした。とりあえずruby/rubyよんで気になったとこを見ようかなという気持ちです。
特に目的も定めずにざーっと読んで読んで、読み進めて。
気になったのが array.c の Array#uniq!
の実装。frozen チェックのあたりがちょっと気になります。
static VALUE rb_ary_uniq_bang(VALUE ary) { VALUE hash; long hash_size; rb_ary_modify_check(ary); if (RARRAY_LEN(ary) <= 1) return Qnil; if (rb_block_given_p()) hash = ary_make_hash_by(ary); else hash = ary_make_hash(ary); hash_size = RHASH_SIZE(hash); if (RARRAY_LEN(ary) == hash_size) { return Qnil; } rb_ary_modify_check(ary); / * ary を削ってからいれるような処理 */ }
rb_ary_modify_check
は freeze 状態だったら例外を投げる的なやつです。
でも関数の最初のほうで rb_ary_modify_check
をしているのにもう1回やってます。どういう理由でしょう。
最初に読んだときは「マルチスレッド対応かな?」と思っていたのですが、質問してみるとそうではないとのこと1。
それより rb_block_given_p
が truthy のときではないかとアドバイスを受けました。
Ruby の Array#uniq!
はブロックをとると、その評価値をもとに uniq をしてくれる仕様です。このブロック評価時に freeze させた場合のためのチェックのようですね。
blame してみるとたしかにそういったコミットがひっかかります。
そんでなんやかんやしてたらすぐに時間がきて終了。パッチは作ったものの、既存の動作を変更するのでどうしたもんかなーと悩み中です2
できたらパッチ送りたかったけど送れず……。次回はもう少し先にアタリをつけておいてすすめたいですね。
次回が来月にあるということなので次回はパッチ投げるところまですすみたいものです
-
なんでそうではないんだか忘れたんだけどGVLがある中で、(ブロック評価とかを除いて)Cの関数実行中にコンテキストスイッチが入らないみたいな話だった気がする……↩
-
まだ悩んでる↩
Ruby本体読みはじめた人に届けたいドキュメント
RubyKaigi2017に参加してやる気が高まったので最近は ruby/ruby のコードを読むようになってます。
しかしこれがなかなか読むのにコツがいる感じです。
RubyKaigi参加前は “Ruby under a microscope”1 を読んで「Rubyの内側面白い!!!」ってなってたのですが、実際にCのソース読みはじめると全然違いますね。マクロ多いし、C言語のこともわからんし……。
Ruby Hacking Guide こと 『Rubyソースコード完全解説』2 を読んだほうがいいのかもと悩むようになりました。
でもそんな悩みも今日解決されました。これ読めばとりあえずはRubyのレポジトリを泳げるようになりそうです。
ruby/extension.ja.rdoc at trunk · ruby/ruby · GitHub
これが非常によいです。まず読んでて面白いし噛み砕かれている感じがある。飽きずに読める感じです。
RubyKaigiに参加して Ruby 本体読むぞーって人はまずここからやってみてはいかがでしょうか。
いまだいたい25%くらい読んだのでメモった内容(というかslackログ)を以下に記しておきます。
まだまだわからんことだらけですのでこれ読んどけとか、このへんから入るのが手頃とか教えてもらえるとうれしいです〜
せっかくだからメモっておくか
INT2FIX() :: もとの整数が31bitまたは63bit以内に収まる自信 がある時
「自信がある時」
rb_str_resize(VALUE str, long len) :: Rubyの文字列のサイズをlenバイトに変更する.strの長さは前 以てセットされていなければならない.lenが元の長さよりも短 い時は,lenバイトを越えた部分の内容は捨てられる.lenが元 の長さよりも長い時は,元の長さを越えた部分の内容は保存さ れないでゴミになるだろう.この関数の呼び出しによって
“”“ゴミになるだろう”“”
さっきから思ってたんだが rb_ary_new2 とか rb_ary_new3とかあるのな
~/.g/g/h/ruby ❯❯❯ git grep rb_ary_new2 | wc -l 123 ~/.g/g/h/ruby ❯❯❯ git grep rb_ary_new3 | wc -l 110
4まであって草
VALUE rb_define_class_under(VALUE outer, const char *name, VALUE super)
なんかしてるのかなーって思ったら定義済みの定数があるか探して、あればクラスであること、スーパークラスが同じであることを確認、なければ普通にクラスつくってからouterにassign
まあ想定どおりの動きであった
これ rb_define_class も rb_cObject に対して同じことやってね?
ああ、でも細かいところの処理違うのだな。
これらの関数の argcという引数はCの関数へ渡される引数の数(と 形式)を決めます.argcが0以上の時は関数に引き渡す引数の数を意 味します.16個以上の引数は使えません(が,要りませんよね,そ んなに).実際の関数には先頭の引数としてselfが与えられますの で,指定した数より1多い引数を持つことになります.
cfuncは必須引数15個までなのか
void rb_add_method_cfunc(VALUE klass, ID mid, VALUE (*func)(ANYARGS), int argc, rb_method_visibility_t visi) { if (argc < -2 || 15 < argc) rb_raise(rb_eArgError, "arity out of range: %d for -2..15", argc);
argcが負の時は引数の数ではなく,形式を指定したことになります. argcが-1の時は引数を配列に入れて渡されます.argcが-2の時は引 数はRubyの配列として渡されます.
-2のとき……
~/.g/g/h/ruby ❯❯❯ git grep 'rb_define_method.*-2' enumerator.c: rb_define_method(rb_cYielder, "yield", yielder_yield, -2); enumerator.c: rb_define_method(rb_cYielder, "<<", yielder_yield_push, -2); ext/win32ole/win32ole_typelib.c: rb_define_method(cWIN32OLE_TYPELIB, "initialize", foletypelib_initialize, -2); ext/win32ole/win32ole_variant.c: rb_define_method(cWIN32OLE_VARIANT, "initialize", folevariant_initialize, -2); io.c: rb_define_method(rb_cARGF, "initialize", argf_initialize, -2); thread.c: rb_define_method(rb_cThread, "initialize", thread_initialize, -2);
-2なんで使うのかよくわからんかった
Cの世界を通らずにそのままRuby to Rubyで動かせるからかな
-
https://www.amazon.co.jp/dp/B01IGW56CU/ 『Rubyのしくみ』なんだけど『言語のしくみ』と紛らわしいのでよくマイクロスコープ、アンダーアマイクロスコープと呼ばれている気がする。↩
-
https://www.amazon.co.jp/dp/4844317210 中古が高いことで有名↩
RubyKaigi 1日目メモ
改行いれるのめんどかったからmdをWYSIWYGモードで。ほぼただのメモです
---
# RubyKaigi 1日目
## introduction
### まつださん
こんくらいなら聞き取れるけど英語スピーカーのトークは大丈夫だろうか
Brazilから来てる人もいる
### マネーフォワード
「3人のRubyコミッターがいる暮らし」
## day1 10:30~
http://rubykaigi.org/2017/presentations/n0kada.html
patch monster
ゆるふわ
fulltime ruby committer at Salesforce (Heroku)
Matzチーム
- Matz
- Nobu
- ~Ko1~
Ruby is older than Git
Window is not supported officially(Git)
taking inventory of bug tickets 棚卸し
how to build ruby
configure + make
Out-of-Place build #とは
baseruby miniruby
baseruby
parse.y -> parse.c
defs/id.def -> id.h
insns.def -> vm.inc
miniruby
no dynamic loading = runnable alone
convenient for debuggng
2.4までのparallel build
building miniruby -? parallel
building extension -> parallel
extconf.rb -> seequential
2.5 はそこをディレクトリ親子関係の依存関係以外は別プロセスで parallel でやるぞって話
https://gist.github.com/hkdnet/1c42ff3b807048cf548763326a7ec2e0
EXPR_END: 式がおわってもおかしくないところ
EXPR_LABEL: キーワード引数の名前がかけるところ
No eye-catcher in 2.5
2.3 safe navigation
Kernel#yield_self
rejected:
neko operator
user-defined operator
under discuttion:
method extraction operator
rightward assignment
## day1 13:00〜
http://rubykaigi.org/2017/presentations/ko1.html
Fiberは明示的な感じ
状態をもつものを、どこで実行状態をとめてるかに依存させたり
capturing continuation: callcc
thread より軽いから fiber
coroutine → どれを実行するのが指定するタイプの concurrency の話?
どこにでもとべるからパワフルなんだけど難しい
動いてるものを親としてresumeしたら子供として動く
先祖をresumeしたらエラー
Fiberの実装
コンテキストのスイッチ: スレッドの状態(pc, sp), VM stack, machine stack(C言語レベルのスタック)
最初はコピってstore, restoreする
中盤でOSの機能とかをつかうようにした
最近スレッドの状態をコピるのをやめた
(すこしは改善したりなんだり)
Guildとかで使うよてい
Auto Fiber proposal → インタプリタでスケジューラが動くようにしておく?
勝手に切り替わるようになってブロッキング時間を有効に使えるかも
でも逆に勝手に変わっちゃうから次にどのFiberが使われるかわかんないし、synchronizationが不要
こういうのは非決定的なバグをうみかちだからあんまり好きじゃない(_ko1)
??「Fiberっぽくないから名前かえたい」
## day1 13:50~
休憩しておりました
## day1 14:40~
http://rubykaigi.org/2017/presentations/kddeisz.html
read, tokenize, build ast, build iseq, exec iseq
Ruby 1.8まではASTを直接
ASTからinstruction sequencesへ
Ruby1.9かあら(だっけ)Iseq使う
2.3からiseqの読み書きができるように
ex:
yomikomu https://github.com/ko1/yomikomu
bootsnap https://github.com/Shopify/bootsnap
ソースを文字列で持ってるからいじれる!!!
gsubしてどうにかする → ここgsubなのか
日→秒変換: 24 * 60 * 60
掛け算はメソッド呼び出しなのでうんぬん
memo: コンパイル時に計算したいのかな
gem: vernacular
https://rubygems.org/gems/vernacular
https://github.com/kddeisz/vernacular/
自分で生成したIseqを食わせる
tuby使う
https://github.com/kddeisz/tuby
memo: JVMみたいにフロントの言語増えたらいいよねみたいなこと言ってた気がする
memo: iseqの仕様って安定的なんだっけね
→Q&A で補足。かわるかもね
## day1 15:50〜
http://rubykaigi.org/2017/presentations/watson1978.html
Ubiregi
パフォーマンス・チューニング
→まずは計測だ!
ツールとしてはiprofilerを使っている
```
$ iprofiler -tiemprofiler ./minruby ~/benchmark.rb
```
```
loop do
hash1.merge(hash2)
end
```
iprofilerでボトルネックを探して最適化する
メソッドを実行する処理 = dispatch メソッド探索 + execution メソッドの実実行
dispatchは広範囲に及ぶ処理なので影響範囲がデカかったりすでに最適化済みだったりする、みたいなことを言ってた
あるメソッドの execution 時間を短くしよう!
- メソッドディスパッチを減らす(実行中に別メソッドを呼んでいるので)
- 冗長なアロケーションを減らす
memo: ディスパッチを減らすというのは具体的にはインライン化とか
## day1 16:40~
http://rubykaigi.org/2017/presentations/narittan.html
streaming data importing
gatewayserver with h2o
mruby
benchmark
## day1 17:30
http://rubykaigi.org/2017/presentations/rubylangorg.html
sponsored: cookpad
よしおりさん
Ruby Commiters Sponsor
What will you do if a better language than Ruby comes along?
Make Ruby even stronger!!!
RHCとかね
Static type checking
mameさんクッ社へ fulltime committer
Ruby program を ロバストに
使い捨てじゃないプログラムを書くようになってきているので無理がきている
そこをなんとかしたい
型注釈絶対いれたくないのか問題
1. コードに書いてもいいよ
2. 書かせたくない
3. コメントになら書かせてやってもいいぞ
type inference がすすんだときにコード側に書いてあるとつらい
structual type はドキュメントに使いにくい
青春のアフター4を読んだ
以下ネタバレ前回でいきますのでご注意ください。
続きを読む転職して1ヶ月が経っていたようです
タイトルのとおりです。
いつものも置いておくのでよろしくお願い致します。誰からかわかるとうれしいです
(毎度のことなんだけどkindle版をほしいものリストから贈れるようにしてほしいし、並び替えはかんたんにできるようにして欲しいし、並び順は覚えておいて欲しいんだけどなあ……)
さて、2017年6月末日づけで某社を退職し、7月1日づけで、某お金を前にするような会社に勤めています。
職種としてはいわゆるWEB系プログラマー(サーバー寄り)という感じです。変わんないですね。
インフラまわりに手を出さなくなったのが前職との違いだと思います。
これは転職とはあんまり関係ないのですが、ついでにせっかくの機会なので一人暮らしを始めてみました。
さらには脱vimしてRubymineを使ってみたりスプラトゥーン2で3D酔いしてみたりしています。
引っ越しのドタバタで6月末の振り返りをすっとばし、なんやかんやで7月の振り返りもすっとばし現在に至ります。
環境が変われば現在の自分に感じる課題というのも変わっていて最近は日々頭を抱えているのですが、うまく言葉にできなくてなんだかにゃーとずっと言っています。
ぜんぶうまくいけ
さて、繰り返しにはなりますが、そんな悩めるカエルへの温かいご支援をお待ちしておりますのでなにとぞ