2.1K Views
December 23, 21
スライド概要
https://omotesandorb.connpass.com/event/231730/
Ruby / Vim / C++
5分で話せる Ruby 3.1 Omotesando.rb #69
自己紹介 名前: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
5分で話せる Ruby 3.1
デバッガとして新しく 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
列単位でエラー箇所を表示する error_highlight が追加
以下のようなコードでエラーになった時にどこでエラーになっているのか分かりづらい
1
2
ruby -e "{user:'homu'}[:ser][:name]"
-e:1:in `<main>': undefined method `[]' for nil:NilClass (NoMethodError)
列単位でエラー箇所を表示する error_highlight が追加
以下のようなコードでエラーになった時にどこでエラーになっているのか分かりづらい
1
2
ruby -e "{user:'homu'}[:ser][:name]"
-e:1:in `<main>': undefined method `[]' for nil:NilClass (NoMethodError)
`error_highlight`
1
2
3
4
5
は次のようにどこでエラーになっているのかを表示してくれる機能
$ ruby -e "{user:'homu'}[:ser][:name]"
-e:1:in `<main>': undefined method `[]' for nil:NilClass (NoMethodError)
{user:'homu'}[:ser][:name]
^^^^^^^
Hash リテラルで参照する値がキー名と同じなら省略できる Hash リテラルを定義する時に値を省略するとキーと同じ名前の変数やメソッドを参照す るようになる 1 2 3 4 def name "homu" end 5 6 7 8 age = 42 9 10 11 12 13 14 15 16 値を省略するとその名前の変数やメソッドを参照する と同じ意味 # # { id: 1, name: name, age: age } homu = { id: 1, name:, age: } pp homu # => {:id=>1, :name=>"homu", :age=>42} リテラルだけではなくてメソッドのキーワード引数としても渡せる と同じ意味 # Hash # pp(name: name, age: age) pp(name:, age:) # => {:name=>"homu", :age=>42}
パターンマッチの `^` が式を受け取るようになった パターンマッチの `^` で直接式をかけるようになった 今までは以下のように変数に代入する必要があったんですが 一度変数に入れてから で変数を参照する必要があった 1 2 3 # ^ range = Time.new(2010)..Time.new(2020) 4 5 6 7 case data in { created_at: ^range } # data.created_at range end が 内であればここが呼ばれる
パターンマッチの `^` が式を受け取るようになった パターンマッチの `^` で直接式をかけるようになった 今までは以下のように変数に代入する必要があったんですが 一度変数に入れてから で変数を参照する必要があった 1 2 3 # ^ range = Time.new(2010)..Time.new(2020) 4 5 6 7 case data in { created_at: ^range } # data.created_at range end が 内であればここが呼ばれる Ruby 3.1 からは以下のように直接式を書くことができる 1 2 3 4 5 で直接式を書くことができるようになった # ^ case data in { created_at: ^(Time.new(2010)..Time.new(2020)) } # data.created_at range end が 内であればここが呼ばれる
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
5
6
7
8
9
10
11
Ruby!
12
13
14
15
# 3.1 : no warning
user => { name:, age: }
p name # = "homu"
p age
# = 14
# 3.0 : warning: One-line pattern matching is experimental, and the behavior may change in future versions of
# 3.1 : no warning
if user in { name: String, age: (..20) }
puts "OK"
end
## `refine` 内での `include / prepend` が非推奨になった `refine` `-W` 内での `include / prepend` が非推奨になった を付けて実行すると警告が表示される 背景としては以下のように意図しない挙動が多発していたため 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 using Module.new { module Twice def twice self + self end end refine String do include Twice end refine Integer do include Twice end } pp "homu".twice pp 42.twice
意図しない挙動例 1 2 3 using Module.new { module Twice def twice = self + self 4 5 6 7 8 def double = twice end refine String do 9 10 11 import_methods Twice end 12 13 14 15 16 17 18 19 20 21 22 23 refine Integer do include Twice end } # OK : Twice#double p "homu".twice 内から Twice#twice を呼び出せる 内から メソッド内では が呼び出せない してないから # NG : Twice#double Twice#twice # error: `double': undefined local variable or method `twice' for 42:Integer (NameError) # double using … p 42.double なぜなら
`import_methods` が追加された の変わりに `refine` 内でモジュールを組み込む機能として `import_methods` が追加された `include / prepend` 1 2 3 4 using Module.new { module Twice def twice self + self 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 end end refine String do import_methods Twice end refine Integer do import_methods Twice end } 使い方は一緒 # pp "homu".twice pp 42.twice
を利用すると継承リストにモジュールが追加されるので はなくてモジュールのメソッドが直接 `Refinements` オブジェクトに定義される `Refinement#import_methods` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class X; end module M1 def hoge; end refine X do include M1 end end # X の継承リストに追加される module M2 def foo; end refine X do # M2 Refinements import_methods M2 end end using M1; using M2 のメソッドが p X.instance_method(:hoge).owner p X.instance_method(:foo).owner オブジェクトに直接追加される # => M1 #=> #<refinement:X@M2>
`Class#descendants / subclass` `Class#descendants` 1 2 3 4 class A; end class B < A; end class C < B; end 5 6 7 8 # p A.descendants p B.descendants p C.descendants が追加された は継承されている全てのクラスの一覧を取得するメソッド クラスがどのクラスで継承されているのかを返す #=> [B, C] #=> [C] #=> []
`Class#descendants / subclass` `Class#descendants` 1 2 3 4 class A; end class B < A; end class C < B; end 5 6 7 8 # p A.descendants p B.descendants p C.descendants は継承されている全てのクラスの一覧を取得するメソッド クラスがどのクラスで継承されているのかを返す #=> [B, C] #=> [C] #=> [] `Class#subclass` 1 2 3 4 5 6 7 8 が追加された class class class class は直接継承されているクラスの一覧を取得するメソッド A; end B < A; end C < B; end D < A; end A.subclasses B.subclasses C.subclasses #=> [D, B] #=> [C] #=> []
無名なブロック引数を別のメソッドにフォワードする 仮引数なしの `&` でブロック引数を受け取り `&` で別のメソッドに渡すことができる 1 2 3 4 def foo(a) yield a end 5 6 7 def bar(&) # foo(1, &) 8 9 10 ブロック引数を foo にフォワードする end bar { p _1 }
無名なブロック引数を別のメソッドにフォワードする 仮引数なしの `&` でブロック引数を受け取り `&` で別のメソッドに渡すことができる 1 2 3 4 def foo(a) yield a end 5 6 7 def bar(&) # foo(1, &) 8 9 10 ブロック引数を foo にフォワードする end bar { p _1 } また `&` を省略する事はできない 1 2 def foo(&) = bar(&) # OK def foo = bar(&) # NG
`Enumerable#each_cons / each_slice` `Enumerable#each_cons / each_slice` 1 2 3 4 [1, 2, 3].each_cons(2){} # 3.0 => nil # 3.1 => [1, 2, 3] 5 6 7 [1, 2, 3].each_slice(2){} # 3.0 => nil # 3.1 => [1, 2, 3] の戻り値が変わった の戻り値が `nil` からレシーバに変わった
`Enumerable#each_cons / each_slice` `Enumerable#each_cons / each_slice` 1 2 3 4 [1, 2, 3].each_cons(2){} # 3.0 => nil # 3.1 => [1, 2, 3] 5 6 7 [1, 2, 3].each_slice(2){} # 3.0 => nil # 3.1 => [1, 2, 3] の戻り値が `nil` からレシーバに変わった これの影響で RuboCop が壊れた 修正コミット 1 2 3 4 5 6 7 8 def block_end_align_target(node) lineage = [node, *node.ancestors]# target = lineage.each_cons(2) do |current, parent| break current if end_align_target?(current, parent) end 以下のようなコードが書かれていた target || lineage.last end の戻り値が変わった
`Kernel#load` `Kernel#load` に `Module` を指定する事でその `Module` 内で 1 2 3 4 # test.rb def hoge = "hoge" 5 6 def foo = "foo" end 1 module M; end 2 3 4 # M test.rb load "./test.rb", M 5 6 7 8 9 10 11 12 13 14 に `Module` を指定できるようになった class Foo に対して の中身が定義される p M::Foo # => M::Foo p M::Foo.new.foo # => "foo" class X include M public :hoge end p X.new.hoge # => "hoge"