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

5分で話せる Ruby 3.1

osyo
December 02, 2021

5分で話せる Ruby 3.1

osyo

December 02, 2021
Tweet

More Decks by osyo

Other Decks in Programming

Transcript

  1. 自己紹介 名前:osyo Twitter : @pink_bangbi github : osyo-manga ブログ :

    Secret Garden(Instrumental) Rails エンジニア 好きな Ruby の機能は Refinements Ruby 3.1 で楽しみな機能は Hash のショートハンドと debug.gem RubyKaigi Takeout 2021 Use Macro all the time ~ マクロを使いまくろ ~ Advent Calendar やってるよ! Ruby の TracePoint を使ってメソッド呼び出しをトレースしよう 一人 bugs.ruby Advent Calendar
  2. デバッガとして新しく debug.gem が本体にバンドルされる debug.gem というデバッグ用の gem が Ruby 3.1 からバンドルされる

    これを利用すると byebug のようなデバッグや VSCode や Chrome 上でビジュアル的に デバッグを行うことができる また Ruby 2.6 以上であれば gem install debug することで debug.gem を利用できる 詳しくは debug.gem の README を読んでね 銀座Rails で登壇したときの動画公開されているのでそれを見るのもおすすめ https://www.atdot.net/~ko1/activities/ginzarails38_06__ko1.mp4 ` ` ` `
  3. 列単位でエラー箇所を表示する error_highlight が追加 以下のようなコードでエラーになった時にどこでエラーになっているのか分かりづらい 1 ruby -e "{user:'homu'}[:ser][:name]" 2 -e:1:in

    `<main>': undefined method `[]' for nil:NilClass (NoMethodError) error_highlight は次のようにどこでエラーになっているのかを表示してくれる機能 1 $ ruby -e "{user:'homu'}[:ser][:name]" 2 -e:1:in `<main>': undefined method `[]' for nil:NilClass (NoMethodError) 3 4 {user:'homu'}[:ser][:name] 5 ^^^^^^^ ` `
  4. Hash リテラルで参照する値がキー名と同じなら省略できる Hash リテラルを定義する時に値を省略するとキーと同じ名前の変数やメソッドを参照す るようになる 1 def name 2 "homu"

    3 end 4 5 age = 42 6 7 # 値を省略するとその名前の変数やメソッドを参照する 8 # { id: 1, name: name, age: age } と同じ意味 9 homu = { id: 1, name:, age: } 10 pp homu 11 # => {:id=>1, :name=>"homu", :age=>42} 12 13 # Hash リテラルだけではなくてメソッドのキーワード引数としても渡せる 14 # pp(name: name, age: age) と同じ意味 15 pp(name:, age:) 16 # => {:name=>"homu", :age=>42}
  5. パターンマッチの ^ が式を受け取るようになった パターンマッチの ^ で直接式をかけるようになった 今までは以下のように変数に代入する必要があったんですが 1 # 一度変数に入れてから

    ^ で変数を参照する必要があった 2 range = Time.new(2010)..Time.new(2020) 3 4 case data 5 in { created_at: ^range } 6 # data.created_at が range 内であればここが呼ばれる 7 end ` ` ` `
  6. パターンマッチの ^ が式を受け取るようになった パターンマッチの ^ で直接式をかけるようになった 今までは以下のように変数に代入する必要があったんですが 1 # 一度変数に入れてから

    ^ で変数を参照する必要があった 2 range = Time.new(2010)..Time.new(2020) 3 4 case data 5 in { created_at: ^range } 6 # data.created_at が range 内であればここが呼ばれる 7 end Ruby 3.1 からは以下のように直接式を書くことができる 1 # ^ で直接式を書くことができるようになった 2 case data 3 in { created_at: ^(Time.new(2010)..Time.new(2020)) } 4 # data.created_at が range 内であればここが呼ばれる 5 end ` ` ` `
  7. 1行パターンマッチが Experimental でなくなった Ruby 3.0 から実験的に実装されていた1行パターンマッチが Ruby 3.1 から実験的でなく なった

    1 user = { name: "homu", age: 14 } 2 3 # 3.0 : warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby! 4 # 3.1 : no warning 5 user => { name:, age: } 6 7 p name # = "homu" 8 p age # = 14 9 10 11 # 3.0 : warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby! 12 # 3.1 : no warning 13 if user in { name: String, age: (..20) } 14 puts "OK" 15 end
  8. ## refine 内での include / prepend が非推奨になった refine 内での include

    / prepend が非推奨になった -W を付けて実行すると警告が表示される 背景としては以下のように意図しない挙動が多発していたため 1 using Module.new { 2 module Twice 3 def twice 4 self + self 5 end 6 end 7 8 refine String do 9 include Twice 10 end 11 12 refine Integer do 13 include Twice 14 end 15 } 16 17 pp "homu".twice 18 pp 42.twice ` ` ` ` ` ` ` ` ` `
  9. 意図しない挙動例 1 using Module.new { 2 module Twice 3 def

    twice = self + self 4 5 def double = twice 6 end 7 8 refine String do 9 import_methods Twice 10 end 11 12 refine Integer do 13 include Twice 14 end 15 } 16 17 # OK : Twice#double 内から Twice#twice を呼び出せる 18 p "homu".twice 19 20 # NG : Twice#double 内から Twice#twice が呼び出せない 21 # error: `double': undefined local variable or method `twice' for 42:Integer (NameError) 22 # なぜなら double メソッド内では using してないから… 23 p 42.double
  10. import_methods が追加された include / prepend の変わりに refine 内でモジュールを組み込む機能として import_methods が追加された

    1 using Module.new { 2 module Twice 3 def twice 4 self + self 5 end 6 end 7 8 refine String do 9 import_methods Twice 10 end 11 12 refine Integer do 13 import_methods Twice 14 end 15 } 16 17 # 使い方は一緒 18 pp "homu".twice 19 pp 42.twice ` ` ` ` ` ` ` `
  11. Refinement#import_methods を利用すると継承リストにモジュールが追加されるので はなくてモジュールのメソッドが直接 Refinements オブジェクトに定義される 1 class X; end 2

    3 module M1 4 def hoge; end 5 6 refine X do 7 include M1 # X の継承リストに追加される 8 end 9 end 10 11 module M2 12 def foo; end 13 14 refine X do 15 # M2 のメソッドが Refinements オブジェクトに直接追加される 16 import_methods M2 17 end 18 end 19 using M1; using M2 20 21 p X.instance_method(:hoge).owner # => M1 22 p X.instance_method(:foo).owner #=> #<refinement:X@M2> ` ` ` `
  12. Class#descendants / subclass が追加された Class#descendants は継承されている全てのクラスの一覧を取得するメソッド 1 class A; end

    2 class B < A; end 3 class C < B; end 4 5 # クラスがどのクラスで継承されているのかを返す 6 p A.descendants #=> [B, C] 7 p B.descendants #=> [C] 8 p C.descendants #=> [] ` ` ` `
  13. Class#descendants / subclass が追加された Class#descendants は継承されている全てのクラスの一覧を取得するメソッド 1 class A; end

    2 class B < A; end 3 class C < B; end 4 5 # クラスがどのクラスで継承されているのかを返す 6 p A.descendants #=> [B, C] 7 p B.descendants #=> [C] 8 p C.descendants #=> [] Class#subclass は直接継承されているクラスの一覧を取得するメソッド 1 class A; end 2 class B < A; end 3 class C < B; end 4 class D < A; end 5 6 A.subclasses #=> [D, B] 7 B.subclasses #=> [C] 8 C.subclasses #=> [] ` ` ` ` ` `
  14. 無名なブロック引数を別のメソッドにフォワードする 仮引数なしの & でブロック引数を受け取り & で別のメソッドに渡すことができる 1 def foo(a) 2

    yield a 3 end 4 5 def bar(&) 6 # ブロック引数を foo にフォワードする 7 foo(1, &) 8 end 9 10 bar { p _1 } また & を省略する事はできない 1 def foo(&) = bar(&) # OK 2 def foo = bar(&) # NG ` ` ` ` ` `
  15. Enumerable#each_cons / each_slice の戻り値が変わった Enumerable#each_cons / each_slice の戻り値が nil からレシーバに変わった

    1 [1, 2, 3].each_cons(2){} 2 # 3.0 => nil 3 # 3.1 => [1, 2, 3] 4 5 [1, 2, 3].each_slice(2){} 6 # 3.0 => nil 7 # 3.1 => [1, 2, 3] ` ` ` ` ` `
  16. Enumerable#each_cons / each_slice の戻り値が変わった Enumerable#each_cons / each_slice の戻り値が nil からレシーバに変わった

    1 [1, 2, 3].each_cons(2){} 2 # 3.0 => nil 3 # 3.1 => [1, 2, 3] 4 5 [1, 2, 3].each_slice(2){} 6 # 3.0 => nil 7 # 3.1 => [1, 2, 3] これの影響で RuboCop が壊れた 修正コミット 1 def block_end_align_target(node) 2 lineage = [node, *node.ancestors]# 以下のようなコードが書かれていた 3 target = lineage.each_cons(2) do |current, parent| 4 break current if end_align_target?(current, parent) 5 end 6 7 target || lineage.last 8 end ` ` ` ` ` `
  17. Kernel#load に Module を指定できるようになった Kernel#load に Module を指定する事でその Module 内で

    1 # test.rb 2 def hoge = "hoge" 3 4 class Foo 5 def foo = "foo" 6 end 1 module M; end 2 3 # M に対して test.rb の中身が定義される 4 load "./test.rb", M 5 6 p M::Foo # => M::Foo 7 p M::Foo.new.foo # => "foo" 8 9 class X 10 include M 11 public :hoge 12 end 13 14 p X.new.hoge # => "hoge" ` ` ` ` ` ` ` ` ` `