おはようございます!ピクスタ開発部です。
ピクスタは今年もRubyKaigiのスポンサーとなりました。今年で6回目です!
今年も、RubyKaigiに参加することができなかった方でもイベントの内容が楽しめるように、実況ツイートをしています。この記事は開発部のメンバーが選りすぐりのセッションを紹介します。
The Year of Concurrency
今年のRubyKaigiもMatzのKeynoteから始まりました。
Ruby 3の話をメインに、Performance, Concurrency, Static analysisの改善について話されていました。
中でも静的型チェックについては、型定義標準ライブラリからTypeProfilerがエラーや型推論を行い、それを基にアプリケーション(自前)で型定義(.rbi)ファイルを作成することで、型を意識することが少なくて済むようになるという話がありました。
Ruby 3では型チェック恩恵の受けつつ、「テストも型宣言もDRYではないから好きではない」というMatzの想いも反映されているバージョンになるので、2020年が非常に楽しみです。
Ruby 3 Progress report
本セッション開始前に、福岡県知事による英語のスピーチがありました。福岡県の紹介、スタートアップへの支援の取り組みだけでなく、mrubyの話をされていて技術的関心の高さに驚きました。このサプライズに会場も大いに盛り上がりました。
Ruby 3の目玉である、Static analysis、Performance、Concurrencyのおさらいをした後は、便利なパターンマッチング、BundlerがRubyGemsに将来的に統合される話がありました。また互換性を維持するために、Frozen string literalなど一部の実装は見送られることになりました。
なお、Ruby 3ではキーワード引数の渡し方が厳格に定められており、ハッシュの変数名の前にはdouble splat(**)
をつける必要があります。非互換な変更のため、Ruby 2.7ではRuby 3で動かなくなるコードに警告が出るようになるので、必要に応じてコードを修正する必要があります。
スライドの最後にRubyのロードマップについて発表がありました。2019年のクリスマスにRuby 2.7をリリース予定、できれば2020年内にはRuby 3をリリースしたいとのことです。
本セッションについてより詳しい情報が載っているので、ご興味がある方はこちらをご覧ください。
Write a Ruby interpreter in Ruby for Ruby 3
YARVの開発やGCの性能改善など、Rubyの高速化に携わってきた笹田耕一さんによるRuby 3の実装方針に関するセッションでした。
笹田さんといえば、昨年のRubyKaigiでの発表もそうだったように、近年はRuby 3に導入予定の新しい並行計算モデルであるGuildの開発にコミットされている印象があります。
そのため、最初は「今年はGuildの話をしないのかな?」と思ったのですが、実は今回のセッションの内容もGuildに関係のあるものでした。
では、セッションの内容をざっと振り返ってみましょう。
本セッションが対象としているRubyの処理系はMRI(Matz' Ruby Implementation)です。MRIのほとんどはC言語(以下、単に「C」と表記)で書かれています。例えば、String
のような組み込みクラスは、rb_define_class
、rb_define_method
といったCの関数を使って定義されています。
このように、CでRubyを実装することにはいくつかの問題点があり、笹田さんは次の4つを挙げられていました:
Annotation (meta data) Cだと最適化のために必要な情報が欠落してしまう or 書きづらいため、この助けになるような情報を付与するための何らかのDSLが必要
Performance たいていの場合ではCはRubyよりも速いが、キーワード引数や例外処理などは例外で、これらのケースではRubyの方が速い(!)ということが起きる
Productivity (2と関連していると思いますが)キーワード引数や例外処理は、Rubyに比べてCでは複雑な実装になってしまう
API change for "Context" 冒頭でGuildに関係があると述べた箇所です。Guildを実装するには、現在の実行コンテキストに関する情報をメソッドに渡せるようにする必要があるが、この変更がすごく面倒くさい
各々の詳細については、公開されているスライドを参照していただければと思います。
そして、これらの問題を解決するために、タイトルにもあるように「Ruby(のメソッド定義)をRubyで書く」と良いのではないか、というのが笹田さんの提案になります。
具体的には、RubyとCでRubyを書いてコンパイル時にこれらを統合する、という方式に変更することを考えられているようです。
これだけ聞くと、「ついにRubyも(GoやTypeScriptのように)セルフホスティングをするのか?」という風に思ってしまいますが、例えばString#length
であれば、String
クラスやlength
メソッドはRubyで定義するが、その中身は__C__.str_length
のようなCの関数を呼び出すだけ(いわゆるFFIですね)という形になるそうです。
ここで問題となるのが、このRubyからCの関数の呼び出しが遅かったり、書き換えによって増えたRubyスクリプトの読み込みが遅かったりすると、誰もこの方式に移行しようとしてくれないだろう、ということです。
そこで、前者をC関数の呼び出しを最適化するためのVMの命令列を追加することで、後者をRuby2.3で導入されたバイトコードをバイナリとしてdumpする仕組みを利用して解決したとのことです。
本セッションの前に同じ会場で行われた国分さんのセッションでも、JITの利用を前提とすればCで書かれたメソッドをRubyで書き換えることで高速化が期待できる、という話がありました。
これと併せて、笹田さんの提案するRuby 3x3に向けてRubyを適材適所で使っていくという方針は、一介のRubyプログラマである自分が干渉できる範囲が広がることもあって、とても興味深いセッションとなりました。
A Type-level Ruby Interpreter for Testing and Understanding
www.slideshare.net
午前のセッションで Matz が Ruby 3.0 の目玉は Performance、Concurrency、 Static analysis であると伝えていましたが、このセッションはそのうちの Static analysis(静的解析) に関係する内容です。
本セッションで紹介された Type Profiler(型プロファイラ) は型の明示されていない Ruby コードの型解析を行うものです。 具体的には、Ruby コードを入力として、以下の2つを出力します。
- 型エラーの情報
- 型定義ファイル(独自の記法で書かれたもの)
この二つの存在がまさに Type Profilerを使う目的そのものとなっており、セッションではそれぞれ"Test"/"Understanding"と表現されていました。
型定義ファイルは Ruby コードを "type-level" で実行することにより作成されます。
type-level で実行するとは、例えば「Integer
を引数として渡したら戻り値として String
が返ってくる」というような情報を得ることを意味します。
実際に Ruby コードを実行するときは、「42 を渡したら "42です" が返ってくる」というように具体的な値で表されるので、一段抽象化した状態でコードを実行しているようなイメージとなります。
セッションではその後、様々なケースにおける Type Profilier の具体例が紹介されました。
インスタンスメソッド、インスタンス変数、ブロック、タプル的なデータ構造、再帰などのケースが挙げられており、 Type Profiler の具体的なイメージが湧きました。
と同時に、実装をするとしたら難しそうで面白そうだな、と感じとてもワクワクしました(小並感)
注意点として、Type Profiler は検査対象のコードを実装した人の真の意図を正しく表現するとは限らない、ということが挙げられていました。しかしだからといってそこを完璧にすることを目指しているわけではなく、望むことを考えるとそれを受け入れても good enough なのだ、ということでした。
Type Profiler は銀の弾丸ではない。しかし型チェックをしたいが型を書きたくない Ruby lovers にとっては今のところ唯一の救いの手だ、という言葉が印象的でした。
評価実験の結果を聞く限りでは、まだ正式にリリースできるほどの完成度にはなっていないようでしたが、Ruby 3.0への期待がとても高まり、早く使ってみたいという気持ちになりました。
なお、ここまで書いて気づいたのですが、ご本人によるわかりやすい解説がこちらにありましたので、そちらをみた方がずっとわかりやすいかと思います。
また、以下のリポジトリで実際のソースコードを確認できます。
Pattern matching - New feature in Ruby 2.7
Ruby 2.7のNew featureの一つであるPattern matchingについてのセッションでした。
実装したものはすでにRubyのtrunkにコミット済みとのことでした。仕様は検討中の部分もあるようですが、試してフィードバックをお待ちしています!とのことでした。
Pattern matchingは一般的に「適合したいパターンを予めいくつか定義しておき、マッチした場合はパターンに従ってデータを分解する」と説明できます。それをもとにRubyとしての言語化を行い、新しい予約語は極力定義しない方向で考えた結果、case
文でin
を使用してPattern matchingを実現する方向になりました。
case expr in pattern [if|unless condition] ... in pattern [if|unless condition] ... else ... end
上記のcase
文を使ったPattern matchingですが、基本的には以下のように実行されます。
- パターンが最初に一致するまで、上から順番に実行
- パターンにマッチしない場合は
else
が実行 - パターンにマッチしないかつ
else
がない場合はNoMatchingPatternError
が発生する - ガード節的なことをパターンとともに定義が可能
マッチングできるパターンとしては値(Integer
やString
等)に加えてArray
やHash
にも対応していたりと、Rubyの既存のデータ構造をうまく使えるようになっています。このあたりはスライド後半の設計ポリシーでも説明がされていたのですが、Array
やHash
はRubyでは重要なデータ構造なため、それをサポートすることで更にRuby-ishになるのではということでした。
実際にスライドの中でネストされたJSONデータに対してPattern matchingさせ、age
を参照するというサンプルを紹介されていました。特定の条件下のJSONの値を直感的に取得できるなという印象です。
おわりに
2日目、3日目も実況ツイートしていきます!よければフォローもお願いします!
* * * * *
ピクスタではエンジニアを募集しています!