Slide 1

Slide 1 text

What a cool Ruby-2.7 is !! ⼤阪Ruby 会議02 @joker1007

Slide 2

Slide 2 text

self.inspect @joker1007 Repro inc. CTO 最近はKafka を触っていてJava ばっかり書いている 黒魔術芸⼈

Slide 3

Slide 3 text

Ruby-2.7 の新機能は熱い! implicit block parameter pattern match method reference syntax REPL improvement

Slide 4

Slide 4 text

というわけで、(REPL 以外の) 全部の機能を使って 2.7 以降で使える新しい書き⽅のスタイルを提案する gem を書いてみた ⼀番重要なのはmethod reference syntax

Slide 5

Slide 5 text

joker1007/method_plus Method Extensions for method reference syntax.

Slide 6

Slide 6 text

Usage foo = Foo.new promise1 = foo.:slowly_foo.async(sleep_time: 0.1) promise1.value # => wait and return value pr = foo.:with_args.partial(0.1, b: _any) foo.call(b: 3) # => call foo.with_args(0.1, b: 3) [1, 2, 3].each do |i| foo.:exec_later.defer(i) # => resolve arguments here p 1 # => call exec_later(i) end foo.:heavy.memoize(10) # => save result on foo instance_variable

Slide 7

Slide 7 text

Method オブジェクトが⾃然に取得できるのでMethod クラスに⾊々⽣やせれば便利!( かも)

Slide 8

Slide 8 text

各追加メソッドの実装解説

Slide 9

Slide 9 text

async check_arity(*args) Concurrent::Promises.future_on(:io, *args) do |*args| call(*args, &block) end concurrent-ruby をwrap してるだけで簡単、と思いきや引数のチェックがめんどい。 引数間違いとか実⾏時に即指摘したいが、Ruby の引数チェックはC の実装の中に組込 まれていて再利⽤できない。 特に引数の定義に合わせて適切なメッセージでArgumentError 出すのが…… 。 ( このチェック処理をRuby 側にexport して欲しい気がする。)

Slide 10

Slide 10 text

check_arity こういう構造化情報でパターンが⼀杯あるケースはパターンマッチがめっちゃ便利。 parameters.each do |pr| case pr in [:req, _] req_size += 1 in [:opt, _] opt_size += 1 in [:rest, _] has_rest = true in [:keyreq, name] has_kw = true keyreqs << name in [:key, _] has_kw = true in [:keyrest, _] has_kw = true else end end マッチングと抽出が同時に出来るのが⼤事。

Slide 11

Slide 11 text

補⾜ これは2.6 でも使えるけど、無限Range も便利。 args_range = has_rest ? (req_size..) : (req_size..req_size + opt_size + (has_kw ? 1 : 0))

Slide 12

Slide 12 text

partial lambda でラップしてPlaceholder オブジェクトに置き換えておいて、 call した時に実際に値を嵌め込み直して呼ぶ。 def partial(*args, **kw, &block) ->(*args2, **kw2, &block2) do placeholder_idx = 0 new_args = args.each_with_object([]) do |a, arr| if a.is_a?(MethodPlus::Placeholder) if (args2.size - 1) >= placeholder_idx arr << args2[placeholder_idx].tap { placeholder_idx += 1 }; end else arr << a; end; end # ... new_block = block2 || block call(*new_args, **new_kw, &new_block) end end

Slide 13

Slide 13 text

defer TracePoint です。( いつもの) ブロック呼び出し毎にstacklevel を記録して、合致する時にmethod をcall する TracePoint を動かす。 かなり単純化するとこんな感じ。 stack_level = 0 trace = TracePoint.new(*events) do |tp| if tp.event == :b_call stack_level += 1; next; end if tp.event == :b_return && stack_level > 0 stack_level -= 1; next; end tp.disable call(*args, &block) end trace.enable(target: iseq) # => important 実際は、もうちょっと⼯夫が要る。

Slide 14

Slide 14 text

TracePoint.enable(target: iseq) 2.6 からTracePoint が動作する対象をiseq レベルで絞れる様になった。 つまり特定のブロックや特定のメソッドの中だけで動作するフックが作れる。 しかし、ちょっと困った課題がある。

Slide 15

Slide 15 text

TracePoint をターゲット指定する時の課題 今処理中のブロックのiseq を取得する⽅法が、ほとんど無い。 (1..10).each do |i| # ここで、このdo-end 内のiseq を取るのが困難 end それが取れれば、iseq が持ってる情報が⾊々使えたり、b_return フックで使い易くなる のだが…… 。 ⼀応、抜け道は( ⾃分の知る限り) ⼀つだけある。 ちなみに、メソッドは⾃⾝の処理中に method(:__callee__) でMethod が取れる。

Slide 16

Slide 16 text

DebugInspector CRuby に組込みのAPI だがRuby 側から触れるAPI が無いのでラッパーgem を利⽤する。 # numbered parameter is Good! iseq = RubyVM::DebugInspector.open { @1.frame_iseq(2) } これを利⽤してスタックを遡ってiseq を取得することができる。 このiseq を使うことで、処理中のブロックを抜けた時だけ発動するTracePoint を仕込む ことができる。 ちなみにこのgem のREADME にはこう書いてある。 do not use this library outside of debugging situations.

Slide 17

Slide 17 text

まとめ Method Reference Syntax を使った新しい書き⽅のスタイルを提案してみた。 foo.:long_method.async(:bar) 組込みクラスを拡張する点はちょっと危ういのでRefinements の⽅が良いかも。 パターンマッチもNumbered Parameter も便利! TracePoint のtarget 指定を活⽤しよう。 Iseq はDebugInspector で取れる。