$30 off During Our Annual Pro Sale. View Details »

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. 5分で話せる Ruby 3.1
    Omotesando.rb #69

    View Slide

  2. 自己紹介
    名前: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

    View Slide

  3. 5分で話せる Ruby 3.1

    View Slide

  4. デバッガとして新しく 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
    ` `
    ` `

    View Slide

  5. 列単位でエラー箇所を表示する error_highlight が追加
    以下のようなコードでエラーになった時にどこでエラーになっているのか分かりづらい
    1 ruby -e "{user:'homu'}[:ser][:name]"

    2 -e:1:in `': undefined method `[]' for nil:NilClass (NoMethodError)

    View Slide

  6. 列単位でエラー箇所を表示する error_highlight が追加
    以下のようなコードでエラーになった時にどこでエラーになっているのか分かりづらい
    1 ruby -e "{user:'homu'}[:ser][:name]"

    2 -e:1:in `': undefined method `[]' for nil:NilClass (NoMethodError)
    error_highlight
    は次のようにどこでエラーになっているのかを表示してくれる機能
    1 $ ruby -e "{user:'homu'}[:ser][:name]"

    2 -e:1:in `': undefined method `[]' for nil:NilClass (NoMethodError)

    3
    4 {user:'homu'}[:ser][:name]

    5 ^^^^^^^
    ` `

    View Slide

  7. 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}

    View Slide

  8. パターンマッチの ^
    が式を受け取るようになった
    パターンマッチの ^
    で直接式をかけるようになった
    今までは以下のように変数に代入する必要があったんですが
    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
    ` `
    ` `

    View Slide

  9. パターンマッチの ^
    が式を受け取るようになった
    パターンマッチの ^
    で直接式をかけるようになった
    今までは以下のように変数に代入する必要があったんですが
    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
    ` `
    ` `

    View Slide

  10. 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

    View Slide

  11. ## 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
    ` ` ` `
    ` ` ` `
    ` `

    View Slide

  12. 意図しない挙動例
    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

    View Slide

  13. 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
    ` `
    ` ` ` `
    ` `

    View Slide

  14. 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 #=> #
    ` `
    ` `

    View Slide

  15. 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 #=> []
    ` `
    ` `

    View Slide

  16. 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 #=> []
    ` `
    ` `
    ` `

    View Slide

  17. 無名なブロック引数を別のメソッドにフォワードする
    仮引数なしの &
    でブロック引数を受け取り &
    で別のメソッドに渡すことができる
    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 }
    ` ` ` `

    View Slide

  18. 無名なブロック引数を別のメソッドにフォワードする
    仮引数なしの &
    でブロック引数を受け取り &
    で別のメソッドに渡すことができる
    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
    ` ` ` `
    ` `

    View Slide

  19. 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]
    ` `
    ` ` ` `

    View Slide

  20. 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
    ` `
    ` ` ` `

    View Slide

  21. 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"
    ` ` ` `
    ` ` ` ` ` `

    View Slide