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

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

Avatar for osyo osyo
December 18, 2020

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

Avatar for osyo

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 が無事にリリー

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