RubyKaigi 2019からRuby 2.7 / Ruby 3.0について読み解く

はじめまして、プラットフォーム事業本部の瑚大(ごだい)です。 業務では、主にRubyを使ってWebアプリケーションの開発をしています。

先月、福岡で開催されたRubyKaigi 2019に参加してきました。
RubyKaigiは、Ruby好きが集まるお祭りという感じで、とてもたくさんの良い刺激を受ける事ができました。
そして、RubyKaigiのセッションは全体的にRuby 2.7や3.0に関してのセッションが多かった印象です。

今回はそのRubyKaigi 2019での話を踏まえて、今後のRubyについてレポートしてみたいと思います。

https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20190520/20190520102627_original.jpg

出典元: RubyKaigi 2019 - Goodies

RubyKaigi 2019の感想

私は今年初めてRubyKaigiに参加させてもらったのですが、全体的にスケールが大きいな!といった率直な感想を持ちました。
福岡国際会議場の1000人以上を収容できるメインホールでも立ち見の人が出るほど参加人数は多かったです。

しかも、参加者は日本人ばかりではなく、海外から参加していた方も3~4割ぐらいいました。
下の世界地図の画像(rubyists.map)から分かるように、日本だけでなく、アジア・ヨーロッパ・欧米からの参加者も多数いらっしゃいました。
このマップを見ると、Rubyは世界各国で利用されている言語なんだなと、改めて実感します!

https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20190520/20190520111748_original.jpg


メインホール以外にも会議室が用意されていて、企業ブースが並んだ大広間や畳が敷かれた休憩所など、各セッションの場所以外も充実していました。 さらに、お昼は会場の外に福岡名物のラーメンやもつ鍋などの屋台が用意されていて、連日どこも大繁盛。 夜は、なんと中洲川端商店街という400m以上も続く商店街を全て貸し切って、RubyKaigi 2019のOfficial Partyを行いました。

https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20190520/20190520102612_original.jpg

https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20190520/20190520102622_original.jpg


そんなスケールの大きいRubyKaigi 2019での話と、Rubyのtrunkなどの情報も踏まえて、以下にまとめてました。

Ruby 2.6.3(最新バージョン)

現在(2019.5.17)のRubyの最新バージョンは2.6.3です。
なんとこのバージョンはRubyKaigi 2019の前日(2019.4.17)にリリースされました。
Ruby 2.6.3 リリース
このリリースの一番の話題は何と言っても、日本の新元号「令和」にサポートした事でしょう。

そして、同タイミングでRubyのtrunkに、Ruby 2.7/3.0で予定されている変更もmergeされました。

Ruby 2.7

Ruby 2.7は例年通り2019年度のクリスマス(12/25)にリリースできたら、という@yukihiro_matzさんの願いだそうです。

そして、RubyKaigi 2019のセッションでも直近の旬な話題として、Ruby 2.7の変更点について多く話されていました。

Ruby 2.7での変更点の中から、RubyKaigi 2019で特に話題になっていた2つを以下にピックアップしてみました。

Numbered parameters

Numbered parametersとは、下記のようなブロックで引数を@1で呼び出すことが可能な構文です。

bugs.ruby-lang.org

p [1, 2, 3].map { |i| i + 3 }
#=> [4, 5, 6]
  • @1を利用した場合
p [1, 2, 3].map { @1 + 3 }
#=> [4, 5, 6]

ちなみに似たようなもので、シェルの$1$2や、Clojureの%1%2などがあります。

Numbered parametersに関しては、trunkにmergeされた後も、Numbered parametersについての議論が白熱しています。

bugs.ruby-lang.org

意見としては、「@はインスタンス変数と間違えやすい」というものや、中には「twitterに書きにくい」といった声も上がってきています 。 (twitterで書きにくいってのはたしかにーwと思いましたね。ただ、@1のアカウントは、凍結されていました。)

Numbered parameters構文は、変数名から中身が想定できないので、大きなブロックの中では使うのにためらう気がします。
ですが、1ライナーで書けるような、ちょっとしたブロック構文で使いたくなりそうです!

Pattern Matching

RubyKaigi 2019の前日にtrunkにmergeされており、
Ruby 2.7で試験的に使えるようになっており、Ruby 3.0で正式に導入予定の新しい構文です。
旬なネタであったことから、RubyKaigiでも一際話題になっていました。

Pattern Matchingとはどういう構文かというと、
条件にパターンを記述し、そのパターンにマッチしたデータ構造を持っている場合に、変数に代入しつつ処理を実行することが出来る構文です。

では、@k_tsjさんのsession資料「Pattern matching - New feature in Ruby 2.7」を参考にしながら、実際の挙動を見ていきます。

基本的な記法は、以下のような形でcase/inを記述します。

case [0, [1, 2, 3]]
in [a, [b, *c]]
  p a #=> 0
  p b #=> 1
  p c #=> [2, 3]
end

すると、inで指定した条件にしたがって、条件分岐します。

case [0, [1, 2, 3]]
in [a]
  :unreachable
in [0, [a, 2, b]]
  p a #=> 1
  p b #=> 3
end

Hashにもサポートしています

case {a: 0, b: 1}
in {a: 0, x: 1}
  :unreachable
in {a: 0, b: var}
  p var #=> 1
end

Pattern Matching構文は、JSONデータをハンドリングする場合などに有用そうです。

{
  "name": "Alice",
  "age": 30,
  "children": [
    {
      "name": "Bob",
      "age": 2
    }
  ]
}
  • Pattern Matchingを利用しなかった場合
person = JSON.parse(json, symbolize_names: true)
if person[:name] == "Alice"
  children = person[:children]
  if children.length == 1 && children[0][:name] == "Bob"
    p children[0][:age] #=> 2
  end
end
  • Pattern Matchingを利用した場合
case JSON.parse(json, symbolize_names: true)
in {name: "Alice", children: [{name: "Bob", age: age}]}
  p age #=> 2
end

条件分岐をさせるだけでなく、同時に変数にも代入してくれるので、 ifcase/when構文よりもコードの記述量を削減できることが期待できます。

今回はシンプルなケースでの紹介にとどめましたが、Pattern Matching構文はすでにhashやobjectにも対応しているため、汎用的な活用シーンを想像できますね。
Pattern Matchingについてもっと詳しく知りたいという方は実際に触ってみたり、もしくはRuby 2.7の(実験的)新機能「パターンマッチ」で遊ぶの記事が個人的におすすめです!

ますますRuby 3.0のリリースが楽しみになってきました♪

Ruby 3.0

RubyKaigi 2019では、Ruby 3.0に導入予定のものについても多く語られていました。 Ruby 3.0では以下の3つのテーマを目標にしているそうです。

  • Static Analysis(静的解析)
  • Performance(性能)
  • Concurrency(並行性)

Static Analysis(静的解析)

なんと、Rubyに静的型解析の導入を検討しているそうです。
ただし、他の静的型付け言語同様に型宣言する仕様ではありません。
application codeにて型を宣言するのは@yukihiro_matzさんが強く嫌っており、Rubyの良さが損なわれる可能性があるからです。

では、どのようにするかというと、以下によって実現します。

  1. Type signature format
    ・ 型注釈を入れるのではなく、型定義ファイル(.rbi)を外部ファイルとして持つ
  2. Level-1 checking
    Type Profilerによって、application code.rbiから型の矛盾を警告
  3. Type signature profiling
    Type Profilerによって、application code.rbiを自動生成(必要であれば手動修正)
  4. Level-2 checking
    .rbiファイルを読み取り、静的型チェックを走らせる
    SorbetSteepが開発検討段階

https://cdn-ak.f.st-hatena.com/images/fotolife/o/optim-tech/20190517/20190517145914_original.png

出典元: RubyKaigi2019 Ruby 3 Progress Report - p.4


上記のように、静的型解析を導入するにあたって、TypeSignatureやTypeProfilerやStaticTypeCheckingTool(Sorbet/Steep)が開発検討中とのことですが、いざRuby 3.0以降で静的型解析が導入されることになれば、より堅牢なシステム開発が可能になりそうです。

以下では、RubyKaigi 2019での静的型解析に関連する発表資料です。

A Type-level Ruby Interpreter for Testing and Understanding
State of Sorbet: A Type Checker for Ruby
The challenges behind Ruby type checking

Performance(性能) / Concurrency(並行性)

以前からRuby 3x3というテーマで、Performance向上を図っていました。
その対策の一つとして、Ruby 2.6よりMJITが導入されていて、1.6倍の速度改善が確認されています。

しかし、残念ながらRailsに関しては遅くなる事がわかっています。
(Railsにはたくさんのメソッドが利用されているため、都度のコンパイルではむしろ遅くなってしまいます。)
さらに、メモリ・CPU・I/Oにそれぞれボトルネックがあるので、それぞれについて改善策を講じる必要があります。

また、Concurrency(並行性)というテーマにおいて、
今までRubyではあまり進んでいなかったマルチコアを活用したアプローチを検討しているとのことです。

最適なConcurrencyモデルを提供するために、

  • Guild(Isolates)
  • AutoFiber(AsyncWhatever)

という機能の提供が検討されています。

Guilds(Isolates)では、オブジェクトを分離して、Immutableなオブジェクトを送ることが可能です。
AutoFiber(AsyncWhatever)は、 I/O操作でコンテキストスイッチし、スレッドよりも簡潔に扱えます。
(Node.jsのノンブロッキングI/Oを参考にしているそうです。)

これらに関して、一番名前を決めるのに苦悩しているそうです。
Guildsはゲーム業界から批判が来ているそうで、AutoFiberはそもそもFiberではないため、どちらも新しい名前を検討中です。
(Isolates・AsyncWhatever)内は候補。

後方互換を意識しつつ、今後のことを考えながら(命名も含め)、パフォーマンスを向上させるために機能実現させることはとても難しいとは思いますが、
それほど真剣に今のRubyと今後のRubyを思っているのだな、と発表を聞いていて感じました。

以下は、RubyKaigi 2019でのPerformance(性能) / Concurrency(並行性)に関連する資料です。

The light-weight-jit-compiler-project-for-c ruby
Performance Improvement of Ruby 2.7 JIT in Real World
Write a Ruby interpreter in Ruby for Ruby 3

Keyword Arguments

Ruby 3.0での大きな非互換として、Keyword Argumentsがより厳格になる旨が伝えられました。

Ruby 2系のKeyword Argumentsには複数の予期せぬ挙動( = バグ)が確認されており、それらを解決するための修正となります。

どういう非互換かというと、

def foo(**opt)
end

h = {k: 42}
foo(h)

という場合ではRuby 3.0からはエラーになるとのこと。 上記の場合を回避する方法として、

def foo(**opt)
end

h = {k: 42}
foo(**h)

という書き換えが必要になってきます。

具体的な事例などの詳細はRubyKaigi 2019 Ruby 3 Progress Reportのp.27〜p.34で発表がありました。

Ruby 2.7ではエラーになりうる箇所で警告を出し、Ruby 3.0でエラーを出力するようです。

なので、上記パターンの記述がある場合は、今からでも対応を考えておいた方が良さそうですね。

Ruby 3.0に入らない変更

RubyKaigi 2019では、以前より変更を入れようという動きがあったものに対して、
いくつはRuby 3.0では変更しないという言及がありました。

  • Frozen String Literal
  • Obsolate “?” Literal
  • Obsolate backquotes
  • Deperecate autoload

ただ、Ruby 3.0以降でも変更なしということではないので、引き続きの対応はしておいた方が良さそうです!!

さいごに

Rubyは多くの方に、多くのシステムに利用されています。
なので、今後の開発も賢く決断して行かなければいけない、とおっしゃっていました。 Ruby 2.7やRuby 3.0の変更も互換性を最重要視して検討しているようです。

そういったRubyのユーザーへの配慮が私たちのシステムと生活を守ってくれているのだなと感じたと同時に、 私たちもRubyにおける意図や構想を理解してサービスの開発に取り組むべきだと思います。 例えばバージョンアップとか、Deprecatedになったメソッドを消していくなど。。

そして、来年のRubyKaigiは、松本市で開催するそうです。 Rubyの生みの親である@yukihiro_matzさんの苗字にかけたんですかね。

今年のRubyKaigiよりも近場での開催となるので、ぜひ来年もRubyKaigiに参加したいと思っています!!

OPTiMでは、Rubyを利用した大型プロジェクトがございます。もしRubyにご興味をお持ちでしたら、ぜひ一度ご連絡ください。
(もしかしたら来年のRubyKaigiに行ける可能性も。)

www.optim.co.jp