2020年12月に正式版がリリースされた、Ruby のメジャーアップデート「Ruby 3.0」において実装されている 静的解析 について調べてみました。
最近では爆速で走る静的型付け言語 Go も人気ですが、Ruby で導入された静的型付けはどのようなものなのでしょうか?
既存の型チェックgem
ソースコード内に型注釈を記載し、値の型が合致しているかチェックする gem が既に存在しています。
とは言え、Ruby本体として型注釈を記載するのはあまり推奨されていないようなので、業務で書くプログラムに実際に導入するかとなると微妙なところもあるかもしれません…。
Rubype
- http://gogotanaka.github.io/rubype.github.io/
- メソッドの引数および戻り値の型(クラス)を指定する
- Any型
- ダックタイピング
- シンボル
- メソッド
- 指定したメソッドを呼び出し可能か
- Any型
TypeStruct
- https://github.com/ksss/type_struct
- データの集合を型として定義する
- Golang で謂う所の struct
- Key が固定なら Struct、不定なら Hash を使うと良い
- ArrayOf
- Golang の
[]*type
- Golang の
- Union
- “どちらかの型” を指定(
true
orfalse
、String
ornil
等)
- “どちらかの型” を指定(
- hash_from
- Hash オブジェクトを TypeStruct オブジェクトに変換
- Ruby において Hash のキーを typo すると値は
nil
が返るので、これによるバグが防げる
- Golang で謂う所の struct
Ruby 3.0 での静的解析
Rubype や TypeStruct ではソースコード内で型をあらかじめ指定し、その型に合った値が入っているかをチェックする仕組みでした。
それに対し、Ruby 3.0 では、「型宣言なしで静的型チェック」を実現する第一歩として、RBS と TypeProf が gem として同梱されています。
なお、RBS および TypeProf は gem として提供されており、Ruby 3.0 以前でも gem install rbs
/ gem install typeprof
で利用可能になります。
RBS
- Ruby 3.0 に
rbs
gem が同梱 - https://github.com/ruby/rbs
- Rubyプログラムの型を記述するための言語
- Union型
|
- “どちらかの型” を指定
- Optional型
?
- 指定した型 or
nil
を許容
- 指定した型 or
- Tuples
[ _type_ ]
- 要素の型が決まっている配列
- Interface型
- ダックタイピング
- 型の記述方法
- Union型
RBS による定義は拡張子を .rbs
として、Ruby のソースコード自体とは別に作成するので、「型注釈の記載」とはならないという認識で良さそうです。
RBS の README.md に記載されているサンプルコードでは、クラス内に定義されたインスタンス変数やインスタンスメソッドに対する型指定が行われており、前述の Rubype および TypeStruct で実現していた機能はほぼ使えるようです。
ただし、Ruby では「型宣言なしで静的型チェック」を目指しているとの事ですので、RBS を自分で書いていては効率が悪いです。
そこで、次に紹介する TypeProf といった型解析ツールの出番です。
TypeProf
- Ruby 3.0 に
typeprof
gem が同梱 - https://github.com/ruby/typeprof
- 型推論を行うための型解析ツール
- ソースコード内に定義されたメソッドについて、引数と戻り値の型を推論し、RBSフォーマットで出力する
こちらは 日本語ドキュメント も準備されており理解しやすいと思います。
TypeProf で Ruby のソースコードを読み込む事で、定義されているメソッドの引数および戻り値の型を推定して、RBS フォーマットで出力することができます。
ただし、現状は TypeProf 自体が実験的なプロダクトであり、特異メソッドの使用やメタプログラミング等においては制約もあるため、ドキュメントに記載があるように、意図した通りに型を推論できない場合もあります。
Ruby 3.0 で実装された静的解析を現時点で実際に使うとすれば、ソースコードを TypeProf で解析した結果の RBS を手動で確認・修正する手順が必要であると思われます。
まとめ
Ruby 3.0 では静的解析の実装に加え、パフォーマンスの改善および並行処理への最適化も実現されており、動的か静的かのベースの違いはあるものの、Go 言語との溝を着実に埋めようとしている印象を受けました。
静的解析について、使う or 使わない の選択肢が増えるのは良いことだと思います。
(使うとなった場合も、標準装備で使えるので)
Rubyでの型注釈は非推奨、でも動的な型周りのバグは防ぎたい…という事で Ruby の静的解析は”ニューノーマル”として定着するのか、今後のアップデートと共に注目していきたいと思います。