Upgrade to Pro — share decks privately, control downloads, hide ads and more …

What a cool Ruby-2.7 is !

What a cool Ruby-2.7 is !

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

Tomohiro Hashidate

September 15, 2019
Tweet

More Decks by Tomohiro Hashidate

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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
    して欲しい気がする。)

    View Slide

  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
    マッチングと抽出が同時に出来るのが⼤事。

    View Slide

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

    View Slide

  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

    View Slide

  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
    実際は、もうちょっと⼯夫が要る。

    View Slide

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

    View Slide

  15. TracePoint
    をターゲット指定する時の課題
    今処理中のブロックのiseq
    を取得する⽅法が、ほとんど無い。
    (1..10).each do |i|
    #
    ここで、このdo-end
    内のiseq
    を取るのが困難
    end
    それが取れれば、iseq
    が持ってる情報が⾊々使えたり、b_return
    フックで使い易くなる
    のだが……

    ⼀応、抜け道は(
    ⾃分の知る限り)
    ⼀つだけある。
    ちなみに、メソッドは⾃⾝の処理中に method(:__callee__)
    でMethod
    が取れる。

    View Slide

  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.

    View Slide

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

    View Slide