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

12月25日にリリースされる Ruby 3.0 に備えよう!

osyo
December 18, 2020

12月25日にリリースされる Ruby 3.0 に備えよう!

osyo

December 18, 2020
Tweet

More Decks by osyo

Other Decks in Programming

Transcript

  1. Ruby 3.0 で型周りのサポートや並列処理を⾏うライブラリが⼊る private def value(value) = value => @value

    つらすぎワロタ Ruby 2.7.2 から deprecated warning がデフォルトででなくなる 前回の銀座Rails#26 のハイライト 前回の銀座Rails#26 のハイライト
  2. ⾃⼰紹介 ⾃⼰紹介 名前:osyo Twitter : github : ブログ : 趣味で

    Ruby にパッチを投げたり bugs.ruby で気になったチケットを ブログにまとめたりしてる やってます Ruby で⼀番好きな機能は Refinements 最近 AST から Ruby のコードを復元する記事や Ruby 基礎⽂法最速マ スターって記事を書いたので気になる⼈は読んでみてね やってます @pink_bangbi osyo-manga Secret Garden(Instrumental) ⼀⼈ bugs.ruby Advent Calendar 2020 気になった bugs.ruby まとめ Ruby の AST から Ruby のソースコードを復元しよう 令和時代の Ruby 基礎⽂法最速マスター Ruby 3.0 Advent Calendar 2020
  3. 今⽇話すこと 今⽇話すこと 前々回の で Ruby 3.0 の話をした その時は preview1 時点での話をした

    preview1 から⼤きく挙動が変わった機能や新機能などがあるので再 度 Ruby 3.0 について話に来た 今回は先⽇リリースされた preview2 よりも新しい 12/17 時点での最 新版の Ruby での話をする 銀座Rails#26
  4. 注意 注意 このスライドでは基本的に 2020/12/17 時点での Ruby の開発版で動 作確認を⾏っています 現在進⾏形でまだ開発は進んでおり実際に Ruby

    3.0 がリリースされ た時点で挙動が変わっている可能性があります 実際に使⽤する場合はリリースノート等の情報を参照してください また Ruby 3.0 ではいくつかの機能が実験的に⼊っています その機能を使うと experimental warning が出⼒される この機能は将来的に挙動が変わる可能性があるので注意して使⽤ する
  5. Ruby 3.0 の概要 Ruby 3.0 の概要 Ruby 3.0 は 2020/12/25

    にリリースされます!!! Ruby 3.0 では以下のような機能が導⼊される予定 パターンマッチ(正式) 型周りをサポートする機能 右代⼊(⼀部実験的) ⾮同期IO をサポートする機能 エンドレスメソッド定義(実験的) 並⾏‧並列処理を⾏うためのライブラリ(実験的) ⼀⽅で Ruby 3.0 ではいくつか⾮互換な変更が⼊っている キーワード引数や include / prepend 周り バグ修正された結果、挙動が変わっているものもある バージョンを上げる際には注意する必要がある
  6. Ruby 3.0 の型周りのサポート Ruby 3.0 の型周りのサポート Ruby 3.0 では型周りをサポートするライブラリがいくつか⼊る RBS:

    Ruby で型情報を定義する .rbs ファイルを扱うライブラリ TypeProf: Ruby コードを解析して .rbs を⾃動⽣成するライブラリ Ruby 3.0 では標準では型チェックを⾏う機能は⼊らない steep という外部ライブラリで .rbs を使った型チェックを⾏うこ とができる RBS と TypeProf に関しては前回話したので今回は省略 を参照してね 参照: 前回のスライド TypePorf デモ Ruby 3 の静的解析ツール TypeProf の使い⽅ - クックパッド開発者 ブログ Ruby 3 の静的解析機能のRBS 、TypeProf 、Steep 、Sorbet の関係に ついてのノート - クックパッド開発者ブログ
  7. Ractor ( 実験的) Ractor ( 実験的) Ractor は Ruby で並列処理を⾏うためのライブラリ

    Ruby + Actor の略 Ruby 3.0 では実験的に導⼊される予定 基本的な使い⽅は前回話したときと変わってない preview1 と⽐較して共有可能オブジェクト周りのサポートが追加さ れている Ractor.make_shareable や Ractor.shareable? の追加 Proc オブジェクトが共有可能になる マジックコメント shareable_constant_value で定数が⾃動的に共 有可能オブジェクト化される 共有可能オブジェクトに関しては に詳しく書かれている Ractor に関しては現在進⾏形で開発されているので細かいところが 変わるやも… こちら
  8. 以下のコードを実⾏すると hello と world が混ざって出⼒される # Ractor.new のブロックが並列処理として実⾏される # .new

    の引数をブロックの引数として受け取る事ができる ractor = Ractor.new(10) do |loop_count| loop_count.times do puts :hello sleep 0.3 end end # 並列処理が終了するまでブロッキングする # p ractor.take 10.times do puts :world sleep 0.3 end デモ デモ
  9. ネストしたオブジェクトを共有可能オブジェクトにする 参照:[Feature #17274] Ractor.make_shareable(obj) hash = { a: "a", b:

    [1, 2, 3], c: { d: { e: [4, 5] } } } # 共有可能オブジェクトかどうか判定する pp Ractor.shareable?(hash) # => false # ネストしたオブジェクトを⼀括で共有可能オブジェクトにする Ractor.make_shareable(hash) pp Ractor.shareable?(hash) # => true # ネストして freeze されている pp hash.frozen? # => true pp hash[:b].frozen? # => true pp hash[:c][:d].frozen? # => true
  10. Ractor.make_shareable で Proc オブジェクトが共有可能になる 参照:[Feature #17284] Shareable Proc a =

    1 block = proc { |x| a + x } # 共有可能オブジェクトでない場合エラーになる # error: allocator undefined for Proc (TypeError) # Ractor.new (block) do |block| # block.call(Ractor.receive) # end # Proc を共有可能オブジェクトにする Ractor.make_shareable(block) # 参照している値が変わってもブロックには反映されない a = 3 pp block.call(4) # => 5 # 共有可能オブジェクトだと OK ractor = Ractor.new (block) do |block| block.call(Ractor.receive) end ractor.send(42) p ractor.take # => 43
  11. マジックコメントで⾃動的に定数を共有可能オブジェクト化する 任意の⾏から差し込める [Feature #17273] shareable_constant_value pragma # マジックコメント以下の定数が共有可能オブジェクト化 # shareable_constant_value:

    experimental_everything A = [1, 2, 3] pp Ractor.shareable?(A) # => true # shareable_constant_value を無効化する # shareable_constant_value: none B = "homu" pp Ractor.shareable?(B) # => false # リテラルだったときのみ許可する # shareable_constant_value: literal # OK C = "homu" # error: unshareable expression D = "homu" + "mami"
  12. Scheduler Scheduler IO のブロッキング処理を⾮同期で⾏うことを⽬的とした機能 他の⾔語だと async/await のような機能に近い Fiber を使って⾮同期処理を⾏う Ruby

    3.0 では⾃分で Scheduler インターフェースを定義してそれを Fiber に設定して使⽤する これにより Fiber で⾮同期 IO 処理を記述する事ができる Ruby 3.0 ではこの概念が⼊った 試してみたけど動かなかった… 参照: デモ http を読み込むサンプルコード [EN] Don't Wait For Me! Scalable Concurrency for Ruby 3! / Samuel Williams @ioquatix - YouTube doc/scheduler.md Scalable Web Applications - RubyWorld Conference 2020
  13. require "fiber" require_relative 'scheduler' $stdout.sync = true puts "Go to

    sleep!" # scheduler # https://github.com/ruby/ruby/blob/8e03e3b0baf12b0e470ef7188559097fea95cb Fiber.set_scheduler(Scheduler.new) Fiber.schedule do puts "Going to sleep" sleep(1) puts "I slept well" end puts "Wakey-wakey, sleepyhead"
  14. パターンマッチ パターンマッチ Ruby 2.7 から実験的に⼊った機能で Ruby 3.0 から正式に導⼊される 基本的には Ruby

    2.7 から⼤きくは変わっていない Ruby 3.0 から find パターンがかけるようになった case ["a", 1, "b", "c", 2, "d", "e", "f", 3] in [*pre, String => x, String => y, *post] p pre #=> ["a", 1] p x #=> "b" p y #=> "c" p post #=> [2, "d", "e", "f", 3] end
  15. 1 ⾏ in ( 実験的) 1 ⾏ in ( 実験的)

    パターンマッチを 1 ⾏でかける構⽂ これはパターンマッチと同様に Ruby 2.7 で⼊った これは Ruby 3.0 では実験的な機能になります Ruby 3.0 では少しだけ挙動が変わり真理値を返すようになった case user in { name: String, age: (..20) } end # 上のパターンマッチが 1 ⾏でかける user in { name: String, age: (..20) } # マッチしなかった場合の挙動が変わった { name: "mami", age: 30 } in { name: String, age: (..20) } # 2.7 => raise NoMatchingPatternError # 3.0 => false
  16. 真理値を返すようになったので条件式として利⽤できる user = { name: "mami", age: 30 } #

    warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby! if user in { name: String, age: (..20) } puts "OK" else puts "NG" end users = [ { name: "homu", age: 14 }, { name: "mami", age: 15 }, { name: "mado", age: 14 } ] # warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby! pp users.select { _1 in { name: /^m/, age: (..15) } } # => [{:name=>"mado", :age=>14}]
  17. 右代⼊ ( ⼀部実験的) 右代⼊ ( ⼀部実験的) Ruby 3.0 では左辺の値を右辺の変数に代⼊する構⽂が⼊る これは

    preview1 から全く異なる機能になった なので前回話した内容は全て忘れてください 最新版では真理値を返さない 1 ⾏ in と同じ挙動になった 最新版では真理値を返さない 1 ⾏ in と同じ挙動になった つまり Ruby 2.7 のときの 1 ⾏ in とだいたい同じ挙動 経緯とか [Feature #17371] Reintroduce expr in pat - Secret Garden(Instrumental)
  18. 基本的には 1 ⾏ in と同じだがマッチしなかったら例外が発⽣する # 左辺の値を右辺の変数に代⼊する 42 => value

    user = { name: "mami", age: 15 } # パターンマッチのように特定のキーの要素を束縛できる user => { name:, age: } pp name # => "mami" pp age # => 15 # もっと厳密にパターンを書くこともできる user => { name: String => name, age: (..20) => age } pp name pp age # in とは違いパターンにマッチしなかったら例外が発⽣する user => { name: Integer } # => raise NoMatchingPatternError
  19. 単にローカル変数に代⼊するだけなら警告は出ないが、パターンマ ッチぽい記述をすると警告ができる # no warning 42 => value # warning:

    One-line pattern matching is experimental, and the behavior may change in future versions of Ruby! [42] => [value]
  20. !!!注意!!! 右代⼊はローカル変数のみに代⼊でき、インスタンス変数やグロー バル変数には代⼊できない これはパターンマッチ⾃体がインスタンス変数やグローバル変数 に値を束縛できない為 # ローカル変数に代⼊しようとするとエラーになる # syntax error,

    unexpected instance variable 42 => @value # syntax error, unexpected global variable, expecting local variable or method { a: 42 } => { a: Integer => $value } # パターンマッチでも同様にエラーになる case 42 # syntax error, unexpected instance variable in @value end case { a: 42 } # syntax error, unexpected global variable, expecting local variable or method in { a: Integer => $value } end
  21. 1 ⾏ in と右代⼊の挙動まとめ # warning 42 in value #

    no-warning 42 => value # warning [42] in [value] # warning [42] => [value] [42] in [String] # => false [42] => [String] # => raise NoMatchingPatternError # error 42 in @value # error 42 => @value
  22. エンドレスメソッド定義 エンドレスメソッド定義 Ruby 3.0 から 1 ⾏でメソッドが定義できるようになった end を書かなくてメソッドが定義できるのでエンドレス preview1

    から少しだけ挙動が変わった # end を書かずにメソッドが定義できる def twice(a) = a + a p twice 42 # => 84 # preview1 では () が必須だったが引数がない場合に限って現在は省略できる def value = 42 # = 付きメソッドが定義できないのは現状もそのまま # error: setter method cannot be defined in an endless method definition def value=(value) = @value = value
  23. 補⾜ 補⾜ のでもしかしたら細 かい挙動が変わるかも? 開発者会議で1 ⾏ def について議論がされていた def foo

    = expr # change: allow if there is space between method name and "="; it shall be the same as "def foo; expr; end" def foo() = expr => var # change: it shall be "def foo(); expr => var; end" # 現状はこうなっている def foo() = expr => var # は以下のように解釈される (def foo() = expr) => var
  24. 余談:private def value(value) = value => @value 余談:private def value(value)

    = value => @value 前回 private def value(value) = value => @value がつらいという話をし た preview1 時点では private( { (def value(value) = value) => @value } ) と解釈されていた 現在はどうなったのかというと… ?
  25. # これは @value に右代⼊しようとしているのでエラーになる # (def value(value) = value) =>

    @value と同じ意味 # syntax error, unexpected instance variable def value(value) = value => @value # これは右代⼊ではなくて Hash の要素として @value が参照される # なのでエラー⾃体は preview1 と同じ # private( { (def value(value) = value) => @value } ) # error: `private': {:value=>nil} is not a symbol nor a string (TypeError) private def value(value) = value => @value # エンドレスメソッド定義の優先順位が変わるとエラーが変わる # def foo() = expr => var が def foo(); expr => var; end になると # private(def value(value) = (value => @value)) # になる # ただし、 value => @value で結局エラーになる… private def value(value) = value => @value
  26. Module#include / prepend Module#include / prepend include / prepend 周りのバグがいくつか修正された

    参照: みてね!! これにより⼀部のコードの挙動が変わったりしているので注意する [Bug #7844] include/prepend satisfiable module dependencies are not satisfied [Bug #17038] On master, ancestry edits can lead to duplicates in Module#ancestors Bug #16852 Refining Enumerable fails with ruby 2.7 [Bug #17130] Method#super_method is broken for aliased methods RubyWorld Conference 2020
  27. 最新版では『すでに include 済みのモジュールに対して include する と include 済みのオブジェクトにも継承リストが反映される』という 挙動になってたりする module

    M def twice self + self end end Kernel.include M # => [String, Comparable, Object, Kernel, BasicObject] # 既存のクラスに M が反映されるようになる p String.ancestors # Ruby 2.7 => [String, Comparable, Object, Kernel, BasicObject] # Ruby 3.0 => [String, Comparable, Object, Kernel, M, BasicObject] # M のインスタンスメソッドが呼び出せるようになる p "hoge".twice # Ruby 2.7 => error: undefined method `twice' for "hoge":String (NoMethodError) # Ruby 3.0 => "hogehoge"
  28. 以下のようにすると .ancestors に同じモジュールが複数含まれるよ うになる これにより今までの挙動と少し変わる可能性があるので注意する 参照:Ruby 3.0 で変わる Module#include の挙動

    - Secret Garden(Instrumental) module M1; end module M2; end class X include M1 include M2 end M1.prepend M2 # Ruby 2.7 だとモジュールは重複しないが Ruby 3.0 だと重複するようになる p X.ancestors # 2.7 => [X, M2, M1, Object, Kernel, BasicObject] # 3.0 => [X, M2, M2, M1, Object, Kernel, BasicObject]
  29. 更に次のような順番で prepend を⾏うと最後の prepend が反映され ないようになるので注意する これが原因で Rails の⼀部が壊れた [Bug

    #16973] Rails Active Support unit test fails since 41582d5866 [PR #39697] Use ActiveSupport::ToJsonWithActiveSupportEncoder#to_json for Ruby 2.8.0 module M end # Ruby 3.0 からはこれの prepend が Array にも反映されるようになる Enumerable.prepend M Array.prepend M p Array.ancestors # Ruby 2.7 => [M, Array, Enumerable, Object, Kernel, BasicObject] # Ruby 3.0 => [Array, M, Enumerable, Object, Kernel, BasicObject]
  30. Range リテラルと正規表現リテラルが frozen 化 Range リテラルと正規表現リテラルが frozen 化 Ruby 3.0

    では Range リテラルと正規表現リテラルがデフォルトで frozen 化される 参照: [Feature #15504] Freeze all Range object [Feature #16377] Regexp literals should be frozen pp (1..10).frozen? # 2.7 => false # 3.0 => true pp /dog/.frozen? # 2.7 => false # 3.0 => true range = (1..10) # 2.7 : OK # 3.0 : error: can't modify frozen #<Class:# <Range:0x0000556bacea7fc8>>: 1..10 (FrozenError) range.instance_eval { @hoge = 42 }
  31. Array や String のサブクラスのメソッドが変更 Array や String のサブクラスのメソッドが変更 Array や

    String のサブクラスのメソッドの戻り値が変更された 今までは⼀部のメソッドがサブクラスを返していたが Ruby 3.0 から は Array や String を返すようになった これの影響で Rails の⼀部が壊れた 参照: [PR #40663] Let AS::SafeBuffer#[] and * return value be an instance of SafeBuffer in Ruby 3.0 [Bug #6087] How should inherited methods deal with return values of their own subclass? [Bug #10845] Subclassing String 【Ruby 3.0 Advent Calendar 2020 】Array やString のメソッドの返り 値が変更された話【15 ⽇⽬】 - ゲームリンクスの徒然なる⽇常
  32. Array#flatten や String#capitalize など⼀部のメソッドの戻り値が Array や String を返すようになった 具体的にどのメソッドが変更されたのかは を参照してくださ

    い NEWS class MyArray < Array end p MyArray.new.flatten.class # 2.7 => MyArray # 3.0 => Array class MyString < String end p MyString.new("hoge").capitalize.class # 2.7 => MyString # 3.0 => String
  33. ⾮推奨な機能が削除される ⾮推奨な機能が削除される Ruby 3.0 では⾮推奨だったメソッドがいくつか削除される ちなみに Ruby 2.7.1 では削除対象のメソッドを使⽤している場合 はデフォルトで警告が出ているが

    2.7.2 ではデフォルトで警告が 出なくなっているので注意する もしかしたら 3.0 リリースまでに も削除されるかもし れません 参照: 他のメソッド 【Ruby 3.0 Advent Calendar 2020 】Ruby3.0 で⾮推奨から廃⽌にな るメソッドたち【4 ⽇⽬】 - ゲームリンクスの徒然なる⽇常 # 2.7.1: warning: ENV.index is deprecated; use ENV.key instead # 2.7.2: no warning # 3.0.0: error: undefined method `index' for {...} ENV.index("foo")
  34. private の引数や attr_reader の戻り値が変更( かも) private の引数や attr_reader の戻り値が変更( かも)

    private public protected の引数の受け取り⽅と attr_reader attr_writer att 戻り値が変わります private などに配列を渡すとその配列の要素に対して適⽤されます attr_reader などの戻り値は定義されたメソッド名のシンボルが配列 てきます ⼀応⾮互換になるのでちょっと注意 で、今⽇この機能のマージされたんですがその後に 現在は Revert されています Ruby 3.0 でどうなるのかはまだ未定… 参照: CI がランダムでコ いう報告があり https://github.com/ruby/ruby/commit/982443e6e373f5a3ac22ee495909 [Feature #17314] Provide a way to declare visibility of attributes defined methods in a single expression
  35. class Foo protected [:x, :y] # same as: protected :x,

    :y attr_accessor :foo, :bar # => [:foo, :foo=, :bar, :bar=] instead of `nil` attr_reader :foo, :bar # => [:foo, :bar] instead of `nil` attr_writer :foo, :bar # => [:foo=, :bar=] instead of `nil` alias_method :new_alias, :existing_method # => :new_alias instead of `Foo` end # これを利⽤するとこんな感じで1 ⾏でかけるようになる class Foo private attr_accessor :foo, :bar end
  36. その他 その他 _1 という名前の変数が定義できなくなるよ Numbered parameters と競合するので $SAFE や $KCODE

    がただのグローバル変数になる 2.7 と⽐較して 53 倍早くなった Hash#except が標準に⼊った yaml のパフォーマンスが上がった Object#then みたいに定義済みメソッドと同名の要素が定義でき るようになった Ruby にはオブジェクトを汚染する仕組みがあった - いまブログ irb の複数⾏コードの貼付けがめっちゃ早くなった [Bug #17101] YAML.load_file: Massive slowdown under Ruby 2.7 vs. Ruby 2.4 OpenStruct で既存のメソッドを呼び出せるようになった
  37. まとめ まとめ 今回の内容は 2020/12/17 時点の話で Ruby 3.0 がリリースされるとき には挙動が変わっている可能性もあるので注意してください 今回書ききれなかった機能とかはたくさんあるのでぜひ

    に⽬ を通しておくといいと思います 個⼈的にはパターンマッチが⼀番注⽬の機能 早くパターンマッチを使って気持ちよく Ruby のコードを書きた い… この話を聞いって Ruby 3.0 に興味を持たれた⽅はぜひぜひ preview2 や最新版の Ruby を試してもらえると幸いです Ruby 3.1 には新しい機能を追加したい NEWS
  38. 宣伝 宣伝 毎⽉ Ruby Hacking Challenge in Hamada.rb というイベントが開催され ています

    その名のとおり Ruby 本体をいじったりするようなイベントです Ruby の実装に興味がある⽅は参加してみるといいと思います です 次回は 2021/01/19( ⽕)
  39. 25 ⽇に Ruby 3.0 が無事にリリー 25 ⽇に Ruby 3.0 が無事にリリー

    スされるの楽しみにしていま スされるの楽しみにしていま す!!!! す!!!!