Ruby浦島記 この5年でRubyに起こったアップデートまとめ

仕事で実に5年ぶりにRubyを触ることになったので、この5年でどんなアップデートがあったのかをキャッチアップついでにまとめてみようと思う。

TL,DR

  • 2.6.0から最新の3.2.0までリリースノートを読んでみた
  • 2.xは2.7.xで終わり、3系になった
  • JITを使った高速化や型の静的解析ができるようになった
  • 3系ではキーワード引数と普通の引数が分けられたのが大きなBreaking change
  • パターンマッチやハッシュの値の省略記法など他の言語に実装されている機能が取り入れられた

Rubyのバージョンアップについて

普段Rubyを使っている人にはわざわざ書くまでもない情報だが、Rubyは毎年クリスマスにアップデートがあり、新しいMINOR(MAJOR)バージョンがリリースされる。
ref: Ruby 2.1.0 以降のセマンティックバージョニングについて

また、ノーマルメンテナンス(不具合修正)が行われるのはほぼ直近2世代、セキュリティメンテナンス (脆弱性の修正)が行われるのは直近3世代までで、リリースから3年と3ヶ月強(リリース後3年経った翌3末)にEOLとなる。

直近のRuby Lifecycle Timelines
直近のRuby Lifecycle Timelines
Ruby ブランチごとのメンテナンス状況 より引用

つまり5年間経つと5世代の更新が発生していることになり、だいぶ差があるのではないかと予想される。
ちなみに筆者が最後にRubyを触っていた2018年は2.5.xが最新であった。

リリースノートを追ってみる

2018-12-25 Ruby 2.6.0

www.ruby-lang.org

2.6で取り上げられているアップデートは、

  • JIT [Experimental]
  • RubyVM::AbstractSyntaxTree [Experimental]
  • 主要な新機能
    • Kernel#yield_self の別名として then が追加
    • ASCII以外の大文字でも定数を定義出来るように
    • 終端なしRange
    • Enumerable#chain と Enumerator#+ が追加
    • Proc#<< 、Proc#>> が追加
    • Binding#source_location の追加
    • Kernel#system にexception: オプションを追加
    • Coverage の oneshot_lines モードの追加
    • FileUtils#cp_lr が追加
  • パフォーマンスの改善

となっている。
そういえばRubyJITを取り入れる話やASTを出せるようになる話を聞いていたなーと懐かしく思った。
2.6でExperimentalで導入されたあとJITがどうなったのか調べたい気持ちになった。
あと、 その他の注目すべき 2.5 からの変更点 の項目で記載されているけど、Bundlerが標準添付されるようになったとか、--ri と --rdoc オプションが変わったとかは以前からRubyを触っていた人にとっては結構印象深い変更ではないだろうか。
参考にさせていただいた記事

2019-12-25 Ruby 2.7.0

www.ruby-lang.org

2.7で取り上げられているアップデートは、

  • Pattern Matching [Experimental]
  • REPL improvement
  • Compaction GC
  • キーワード引数を通常の引数から分離
  • 主要な新機能
    • 番号指定パラメータ
    • Enumerable#tally が追加
    • レシーバをselfとしてprivateメソッドを呼び出すことが許容
    • Enumerator::Lazy#eager が追加
  • パフォーマンスの改善

となっている。
界隈の反応を見るに、パターンマッチングが取り入れられたというのが目玉と見て良さそう。
あと3.0に向けてキーワード引数の取り扱いに手を入れられたようだ。
リリースノートでは取り上げられていないが、個人的には全ての引数を受け取る ... 引数が追加されたのは便利そうだなぁと感じた。(ref)

def foo(...)
  bar(...)
end

Rubyで親クラスのメソッドをオーバーライドして処理を追加してsuperを呼びたいときに、superがどういう引数の受け取り方をしているかいちいち調べないといけなかったが、 ... 引数を使えばそれを考えるのを省略できそう。
ちなみにRuby 2.7.0はちょうど今月(2023/03)末でEOLとなるので、今後は3系しか使えなくなる。

参考にさせていただいた記事

2020-12-25 Ruby 3.0.0

www.ruby-lang.org

3.0で取り上げられているアップデートは、

  • パフォーマンスの改善
    • MJIT
  • Concurrency / Parallel
    • Ractor (experimental)
    • Fiber Scheduler
  • 静的解析
  • その他の主要な新機能
    • 1行パターンマッチが再設計された (experimental)
    • findパターンが追加 (experimental)
    • 一行メソッド定義が書けるようになった
    • Hash#except が組み込みに

となっている。
Ruby2.0.0-pのリリースが2013/02/24だったので、実に7年以上ぶりのメジャーアップデートとなった。
自分がRubyを使い始めた2013年がちょうど1.9.3から2.0.0への過度期だったなぁと懐かしい気持ちになった。
5年前時点ではRuby 3.0では型推論が入るとか型が入るとか *1もろもろ騒がれていたけど、最終的に静的解析に落ち着いたのかな(もしくは自分がキャッチアップできていなかっただけで最初から静的解析の話しかなかった?)
メジャーバージョンがあがってbreakingな変更はキーワード引数と通常引数の分離がよく挙げられている印象。
2.xでは雑にhashをメソッドに渡せばよかれに色々やれてたのが便利ではあったけど、正されたのね。

参考にさせていただいた記事 - Ruby 3の静的解析機能のRBS、TypeProf、Steep、Sorbetの関係についてのノート - サンプルコードでわかる!Ruby 3.0の主な新機能と変更点 Part 2 - 新機能と変更点の総まとめ - jnchitoさんの記事がQiitaからZennに移行されましたね - アプリケーションをRuby3にあげるときにやること

2021-12-25 Ruby 3.1.0

www.ruby-lang.org

3.1で取り上げられているアップデートは、

  • YJIT: New experimental in-process JIT compiler
  • debug gem: 新しいデバッガ
  • error_highlight: バックトレース中の詳細なエラー位置表示
  • IRB のオートコンプリートとドキュメント表示
  • その他の主要な新機能
    • ハッシュリテラルやキーワード引数の値が省略可能に
    • パターンマッチ中のピン演算子に任意の式を書けるように
    • 一行パターンマッチで括弧が省略できるように
    • RBS、TypeProfのアップデート

となっている。
2.6でJITが取り入れられて以来Ruby 3×3として高速化が試みられてきたけど、Railsのようなアプリケーションの高速化は達成できていなかったのがここに来て改善できたということらしい。
ここに来て急にデバッグ周りの改善が入ったのも浦島的には謎。何があったんだろう?
個人的には ハッシュリテラルやキーワード引数の値が省略可能に なったのがとんでもない変更だと思う。Rubyistたちはこの無駄な文字列の繰り返しを無限に書き続けてきたのではなかろうか。

x = 1
y = 2
z = {x:, y:} # {x: x, y: y}

def foo(x:, y:) # foo(x: x, y: y) 
end

ただ、バージョンによって動かなくなっちゃうと思うので各種Gemではまだこの書き方は浸透させられない気もする。

2022-12-25 Ruby 3.2.0

www.ruby-lang.org

ここでようやく直近のアップデートまで来た。
3.2で取り上げられているアップデートは、

  • WASIベースのWebAssemblyサポート
  • 実用段階になったYJIT
  • ReDoSに対するRegexpの改善
  • その他の主要な新機能
    • SyntaxSuggest
    • ErrorHighlight
    • 匿名の可変長引数と可変長キーワード引数が引数としても使えるように
  • 新たなコアクラス Data が追加

ここにきてWebAssemblyかーと思ったけど、WebAssemblyをマルチプラットフォームな実行環境として使うっていう試みはRubyじゃないどこかで確かに見た気がする。

新しいコアクラスのDataは今までActiveModelを使って実装してた値を持つだけのクラスを簡単に用意できそうだなっていう印象。今までもStructでできたと思うけどimmutableだったりするらしいので、環境変数やらどこかやらからロードした設定値を持っとくだけのクラスとかを作るのに使えそう。

あと Dir.exists?File.exists? が削除されたってマジ?と思ったらだいぶ前からDir.exist?File.exist? 使えっていうことになってたのね。
チケット見たら3.0.0で消したら?って提案されていたのに3.2になったぽい。多分それくらい忘れられてたってことなのかな。

浦島的まとめ

  • キーワード引数の取り扱いについては認識を改める必要がある
  • JITで高速化されて、実用的なレベルに達した
  • 型については静的解析ができるようになった

ということで思ったより大きな変化は正直なかったのかなと思う。
もろもろ全然変わっていて以前の知識じゃついていけないということはなさそう。
このあたりはPython2 -> 3 が人類にもたらした後方互換性を諦めると色々終わるという知見がエンジニアの脳裏に刻まれているんだろうなって気がする 笑

*1:もともとRubyには最初から全てのオブジェクトに型(クラス)があるのだが