はじめに
今まで2系で使っていたプロジェクトを3系にバージョンアップするにあたって、型検査導入しようと思って調べました。
使用ライブラリ
おそらく型検査とか型推論とか調べると、TypeprofとSteepが出てくるんですが、最初使い分けが分かりにくいのですが、以下資料のこの画像が全てかなと思います。
※「Ruby 3の型推論やってます」より引用
この、ruby-type-profiler = TypeProfですね。
つまり、TypeProfは実コードからrbsファイル(型が記述されたファイル)を生成し、Steepはその型定義から実コードを型検査するという役割です。
もう少し細かく解説するなら、
- TypeProfのメイン機能は型推論なので、例えばVSCodeのプラグイン入れればコード補完や型の間違いを指摘してくれる(ただ、型推論の精度は完璧ではないので適宜自分で型ファイルを修正する)
- rbsファイルを1から自分で作ればTypeProf使わなくてもSteepで型検査できる。こちらもプラグインを入れれば型検査の結果をIDE上で表示してくれます。
TypeProf
以下の記事を参考にTypeProf導入します。
Ruby3.1 静的解析の導入で開発体験を向上させる (RBS, TypeProf)|Offers Tech Blog
色々頑張ったんですが、私の環境だとVSCodeのプラグインは動いてくれませんでしたが、typeprofコマンドで型ファイルを生成するところまではできました。
Steep
以下の記事を参考にTypeProf導入します。
こちらはVSCodeのプラグインが無事動作してくれたので、ちょっと確認してみます。
上記の記事のサンプルコードを使用させて頂きます。上記記事では自分で書いたrbsファイルを使用していました、今回はtypeprof導入済みのため、こちらで生成したrbsファイルを利用してみます。
class Person
attr_reader :name
attr_reader :gender
def initialize(name:, gender:)
@name = name
@gender = gender
end
end
こちらのクラスが記述されているファイルをtypeprofで読み込むと以下のようなrbsファイルが得られます。
# TypeProf 0.21.7
# Classes
class Person
attr_reader name: untyped
attr_reader gender: untyped
def initialize: (name: untyped, gender: untyped) -> void
end
まあ、nameもgenderも何が入るかは全くわからないので、当たり前と言えば当たり前のようにuntypedが入りますね。
一つメソッドを追加して、改めてrbsファイルを生成します。
class Person
attr_reader :name
attr_reader :gender
def initialize(name:, gender:)
@name = name
@gender = gender
end
def hello
"hello, #{name}. You are #{gender}."
end
end
# TypeProf 0.21.7
# Classes
class Person
attr_reader name: untyped
attr_reader gender: untyped
def initialize: (name: untyped, gender: untyped) -> void
def hello: -> String
end
helloの返り値がStringということは推定されていますが、nameもgenderもIntegerが入ってきても特に問題なく処理されるのでuntypedになっていますね。
この状態で、Personをただnewすると、引数が足らないというエラーがちゃんと出てくれますね。
steep check
コマンドをコンソールで叩いても同様のエラーが出ます。
あとは、気になることとしてrbsファイルに書いてあるメソッドが実装されていない場合どうなるか、つまりinterface的に使えるのかちょっと確認してみます。
# TypeProf 0.21.7
# Classes
class Person
attr_reader name: untyped
attr_reader gender: untyped
def initialize: (name: untyped, gender: untyped) -> void
def hello: -> String
def male?: -> bool
end
まあ当たり前といえば当たり前ですね。依存関係をチェックしているわけではなく、間違った型の使われていないかチェックしているのでinterface的な使い方は難しいっぽいです。
これ入れてくれると結構良さげかなと思いました。動的言語にわざわざ型チェック入れてるんだからついでにinterface入れてくれても的な。
まとめ
Rubyの型検査に関してはまだ発展途上な感じがしていて、もうちょっと使いやすくなれば導入可能かなと思いました。
現状、1から導入するのであれば、そこまでハードル高くないですが、既存プロジェクトに適用するとなるとかなり大変だなぁと。