アジェンダ
- キーワード引数が通常引数から分離
- ハッシュリテラルやキーワード引数の値が省略可能になった
- 引数委譲の記法の拡張
- パターンマッチがexperimentalな機能ではなくなった
- 1行パターンマッチ
- 右代入
- in
- 1行パターンマッチ
- 1行メソッド定義
- private attr_reader :fooと書けるようになった
- ブロックを移譲する記法が導入された
- エラー発生位置をわかりやすく示すerror_highlightが導入された
- compaction GC を自動でやってくれる GC.auto_compact = true が追加された
ここから下は興味があったらどうぞ
- Ractor(並列処理ライブラリ)
- Fiber scheduler(I/Oブロッキングのスケジューラ)
- MJIT/YJIT(JITコンパイラの実装)
- RBS(静的解析基盤)
- debug gem(デバッガ刷新)
キーワード引数が通常引数から分離
ruby3からはキーワード引数を渡すときは明示的に渡す必要がある
# キーワード引数を受け取るメソッド
def foo(key: 42); end
foo(key: 42) # OK: キーワード引数を渡している
opt = { key: 42 }
foo(opt) # NG: 普通の引数を渡しているのでエラー(2.7では警告付きで動いていた)
foo(**opt) # OK: ハッシュを明示的にキーワードに変換している
キーワード引数から普通の引数への暗黙的変換は維持されている
# 普通のオプショナル引数を受け取るメソッド
def foo(opt = {}); end
foo(key: 42) # OK: キーワード引数が暗黙的に普通の引数に変換される
# # ↑は動きますが、今後は次のように書くのがおすすめです
# def foo(**opt); end
ハッシュリテラルやキーワード引数の値が省略可能になった
{ x: x, y: y } の省略記法として { x:, y: } と書けるようになった
x = 1
y = 2
# h = { x: x, y: y } と同じ意味
h = { x:, y: }
p h #=> {:x=>1, :y=>2}
また、キーワード引数でも同様の省略ができるようになりました
def foo(a:)
p a
end
a = 1
# foo(a: a) と同じ意味
foo(a:)
最初は、JavaScriptと同じものがRubyにも欲しい、という提案だったが、数学の集合にしか見えないということで、却下されたらしい
// 数学の集合にしか見えないため却下
const x = 1
const y = 2
{ x, y }
パターンマッチがexperimentalな機能ではなくなった
パターンマッチ(2.7)のおさらい
case/in
でパターンマッチできる機能が2.7から試験的に導入
3.0で正式な機能になった
パターンマッチの仕様自体が複雑なため簡単なののみ紹介
case {a: 0, b: 1, c: 2}
in {a: 0, x: 1}
:unreachable
in {a: 0, b: var}
p var #=> 1
end
# どのパターンにもマッチしない場合、case/whenと違いNoMatchingPatternError例外が投げられる
case [1, 2]
in [x, y]
p x #=> 1
p y #=> 2
end
# インスタンス変数はパターンマッチの代入につかえない
case 1
in @val
end
SyntaxError: unexpected instance variable
一行パターンマッチ
右代入
パターンマッチの一部として右代入が導入された
{ a: 1, b: 2, c: 3 } => hash
p hash #=> { a: 1, b: 2, c: 3 }
{ a: 1, b: 2, c: 3 } => { a:, b:, c: }
p a #=> 1
p b #=> 2
p c #=> 3
{ a: 1, b: 2, c: 3 } => { a:, b:, c:, d: } # NoMatchingPatternError(キーワード `d` がないため)
# 例ではjsonの値取得に使っていた
json = <<END
{
"name": "Alice",
"age": 30,
"children": [{ "name": "Bob", "age": 2 }]
}
END
JSON.parse(json, symbolize_names: true) =>
{name: "Alice", children: [{name: child_name, age: }]}
p child_name #=> "Bob"
p age #=> 2
# 括弧が省略可能
[1, 2, 3] => x, y, z
{ a: 1, b: 2, c: 3} => a:, b:, c:
in
caseを省略してinだけでもかける
右代入との違いは返り値にtrue/falseを返すこと
p [1, 2] => [a, b] #=> nil
p [1, 2] in [x, y] #=> true
if json in { type: :account, property: }
p property
end
1行メソッド定義
1行でメソッド定義ができるようになった
def square(x) = x * x
p square(5) #=> 25
# putsに括弧がつけなくてもかける(3.1から)
def foo = puts 'hoge'
private attr_reader :fooと書けるようになった
attr_readerが返す値やprivateに渡せる値が変わったことでアクセス指定子がシンプルにかけるようになった
class Foo
# (1) attr_reader や attr_accessor が定義したメソッドのシンボルの配列を返すようになった
attr_accessor :foo, :bar #=> [:foo, :foo=, :bar, :bar=]
# (2) public や private が配列を引数に受け取れるようになった
private [:foo, :foo=, :bar, :bar=]
# 2 つを組み合わせると、次のように書いても同じ意味になる
private attr_accessor :foo, :bar
end
ブロックを移譲する記法が導入された
ブロックを受ける引数を無名にして渡すことができるようになりました。
def foo(&)
bar(&)
end
# ↓のものと意味的には同じ
def foo(&block)
bar(&block)
end
引数委譲の記法の拡張
2.7から導入された引数以上の...
が先頭引数を受け取れるようになった
def method_missing(meth, ...)
send(:"do_#{meth}", ...)
end
エラー発生位置をわかりやすく示すerror_highlightが導入された
# hogeでエラーが起きたことがわかりやすくなった
~/repo/cabernet$ cat error.rb
'aaa'.to_s.hoge
~/repo/cabernet$ be rails r error.rb
error.rb:1:in `<top (required)>': undefined method `hoge' for "aaa":String (NoMethodError)
'aaa'.to_s.hoge
^^^^^
compaction GC を自動でやってくれる GC.auto_compact = true が追加された
2.7から入っていたGC.compactを自動でやってくれる機能が追加された
詳細はよくわからん & 導入は難しいかもだが面白そうだったので一応紹介
GC.auto_compact = true とすることで、major GC が起こるとコンパクションも実行してくれます。
そのため、定期的にメモリの掃除をしてくれることになり、メモリ効率の向上、および局所性向上による性能改善が期待できます。
が、ここにも書いてある通り、コンパクション自体が結構なオーバヘッドになるので、自分のアプリで効くかどうか確認してみるといいと思います。
デフォルトは、そういうことで false です。
<中略>
正直、まだ実装がこなれていないような気がするので(拡張ライブラリあたりが怪しいです)、みんながすぐにこれを使うってのには、
ならない気がします(はまらなければ、使ってもいいと思います)。
その他大きな変更
興味ある方はどうぞ
RactorとFiberは今後重要になりそうな雰囲気ありますが、自分には難しくてよくわかりませんでした。
Ractor(並行処理ライブラリ)
actor modelを強く意識して実装されたRubyの並行処理ライブラリ
スレッドプログラミングをもっと簡単に実装したいという目的で作成されたらしい。
しかし難しい
Ractor周りの処理を楽にするために値をimmutableにする変更が今後入ってきそう
def tarai(x, y, z) =
x <= y ? y : tarai(tarai(x-1, y, z),
tarai(y-1, z, x),
tarai(z-1, x, y))
require 'benchmark'
Benchmark.bm do |x|
# sequential version
x.report('seq'){ 4.times{ tarai(14, 7, 0) } }
# parallel version
x.report('par'){
4.times.map do
Ractor.new { tarai(14, 7, 0) }
end.each(&:take)
}
end
4 cores, 8 hardware threads CPUでの実行結果
Benchmark result:
user system total real
seq 64.560736 0.001101 64.561837 ( 64.562194)
par 66.422010 0.015999 66.438009 ( 16.685797)
Fiber scheduler(I/Oブロッキングのスケジューラ)
Fiber(軽量スレッド goroutine的なもの)で処理がI/O などでブロックされたら、
他の独立した Fiber を実行するようなスケジューラを、
Ruby で記述するための仕組みが Fiber scheduler です。
今後ActiveRecordとかの内部で非同期処理につかわれる?
MJIT/YJIT(JITコンパイラの実装)
JIT(Just In Time)コンパイラ
必要な分を、必要な時に」コンパイルするコンパイラ
- MJIT MRI (Matz' Ruby Implementation) JIT コンパイラ
- YJIT Shopifyが開発したRailsの高速化を目指したJITコンパイラ
RBS(静的解析基盤)
Rubyで型を定義できるようになった。
- RBS:Ruby の型情報を扱う言語。Ruby 3 にバンドルされる。
- TypeProf:型注釈のない Ruby コードを型解析するツール。Ruby 3 にバンドルされる。
- Steep/Sorbet:Ruby で静的型付けのプログラミングができるツール。
debug gem(デバッガ刷新)
デフォルトのデバッガが便利になったらしい
参考