What a cool Ruby-2.7 is !

What a cool Ruby-2.7 is !

大阪Ruby会議02 発表スライド

A5e5ee2fb9e4ce3c728ed9e3ef6e916f?s=128

Tomohiro Hashidate

September 15, 2019
Tweet

Transcript

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

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

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

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

  5. joker1007/method_plus Method Extensions for method reference syntax.

  6. 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
  7. Method オブジェクトが⾃然に取得できるのでMethod クラスに⾊々⽣やせれば便利!( かも)

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

  9. async check_arity(*args) Concurrent::Promises.future_on(:io, *args) do |*args| call(*args, &block) end concurrent-ruby

    をwrap してるだけで簡単、と思いきや引数のチェックがめんどい。 引数間違いとか実⾏時に即指摘したいが、Ruby の引数チェックはC の実装の中に組込 まれていて再利⽤できない。 特に引数の定義に合わせて適切なメッセージでArgumentError 出すのが…… 。 ( このチェック処理をRuby 側にexport して欲しい気がする。)
  10. 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 マッチングと抽出が同時に出来るのが⼤事。
  11. 補⾜ これは2.6 でも使えるけど、無限Range も便利。 args_range = has_rest ? (req_size..) :

    (req_size..req_size + opt_size + (has_kw ? 1 : 0))
  12. 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
  13. 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 実際は、もうちょっと⼯夫が要る。
  14. TracePoint.enable(target: iseq) 2.6 からTracePoint が動作する対象をiseq レベルで絞れる様になった。 つまり特定のブロックや特定のメソッドの中だけで動作するフックが作れる。 しかし、ちょっと困った課題がある。

  15. TracePoint をターゲット指定する時の課題 今処理中のブロックのiseq を取得する⽅法が、ほとんど無い。 (1..10).each do |i| # ここで、このdo-end 内のiseq

    を取るのが困難 end それが取れれば、iseq が持ってる情報が⾊々使えたり、b_return フックで使い易くなる のだが…… 。 ⼀応、抜け道は( ⾃分の知る限り) ⼀つだけある。 ちなみに、メソッドは⾃⾝の処理中に method(:__callee__) でMethod が取れる。
  16. 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.
  17. まとめ Method Reference Syntax を使った新しい書き⽅のスタイルを提案してみた。 foo.:long_method.async(:bar) 組込みクラスを拡張する点はちょっと危ういのでRefinements の⽅が良いかも。 パターンマッチもNumbered Parameter

    も便利! TracePoint のtarget 指定を活⽤しよう。 Iseq はDebugInspector で取れる。