RubyではRBSという言語で型を記述できます。Ruby 3.
RBSではこの1年でさまざまな機能追加やバグ修正が活発に行われ、520個のファイルに変更が加えられ、56,464行の追加と、26,172行の削除が行われました!
私、栗原はその内の95個のファイルに変更を加え、7474行の追加と2341行の削除に関わりました。
この記事では、この一年RBS界隈を追い続けてきた私から、RBSのアップデートを中心にRubyの型の世界の変化をまとめて紹介します。Rubyで仕事をしている方、RBSに興味はあるけど最新情報は追えていなかった方の力になれれば幸いです。
RBSとは
Ruby 3..c
と.h
、TypeScriptでいう.js
とd.
の関係に似ていると言われています。
# foo.rbs
class Foo
# Integer型を引数にとり、String型を返り値として返すメソッド
def bar: (Integer) -> String
end
# foo.rb
class Foo
# 実装は別ファイル
def bar(i)
i.to_s
end
end
型と一口に言ってもさまざまな利用方法がありますが、RBSではこれまでに、Rubyの静的型チェックツールや型推論、コードの入力補助やドキュメント等、さまざまな使われ方が広まってきています。
rbsはGitHubリポジトリにて活発に開発されています。
ちなみに、本稿では大文字での
RBSがメジャーバージョンアップ
RBSはこの1年でメジャーバージョンアップし、2系から3系に進化しました。といっても内部APIが少し変わったくらいで、これまでのRBSファイルはそのまま使え、新しい構文がいくつか追加されています。追加された構文を簡単に紹介します。
class/moduleの別名が定義可能に
RBSではclassやmoduleを定義できますが、このclass/
class Queue = Thread::Queue
RubyではQueue
と書くとThread::
classとして使用できます。この表現をRBSでも書けるように、新しい構文が追加されました。Rubyとは違い、明示的にclass
やmodule
と最初につけることで別名を定義できます。
ファイル単位でのclass/module名の省略・別名が定義可能に
TypeScriptやPython等でお馴染みの、ファイル単位でのネームスペースの省略ができるようになりました。
use Happy::New::Year as Y
上記のclass/use
構文を使うことで、よりカジュアルに別名を扱えるようになりました。
“あとで直す”型の追加
構文というより型の追加ですが、__
という型が追加されました。
def foo: () -> __todo__
__
はuntyped
の別名扱いですが、__
はuntyped
と同じですが、untyped
はすべての型を許すという意味合いがある一方、__
では修正すべき仮の状態であることがわかりやすくなります。
新構文についてはMoney Forward Developers Blogの
RBSをテストする機能の追加
たとえば、minitestやrspecで記述されたテストコードに、RBSの正確性を担保するテストを追加できると、うれしいと思いませんか? これがついに可能になりました。
# Fooのインスタンスメソッド`#bar`を
# Integer型と共に呼び出すとString型が返ることを確認するテスト
assert_send_type "(Integer) -> String",
Foo.new, :bar, 42
これまでrbsリポジトリー内だけで使える、RBSをテストするための機能があり、coreやstdlibのRBSはこの仕組みによって正確性が担保されています。この仕組みがパブリックAPIとして使用できるようになりました。
せっかくRBSを記述しても、それが正しくRubyコードの挙動を表現できているか保証はありません。Integer
が返るメソッドにString
が返るように書いてしまっているかもしれません。
記述したRBSをテストできれば、RBSの記述が実装に沿った正しいものなのかをテストコードとして記述できるようになります。テストコードにできれば、CIとして確認できるようになり、RBSの新鮮さを保てるようになります。
RBSのテストはまだまだ開拓が進んでいない分野なので、このパブリックAPI化によってさらなる発展が期待されます。
Release noteにもminitestでの使用例が記載されています。
RBSのバリデーション機能が大幅アップデート
rbs validate
コマンドで複数の改修があり、より使いやすく便利になりました。このコマンドは、ほとんどの状況でRBSの整合性チェックとして使える便利なコマンドなので、精力的な改修がなされました。
複数のエラーの報告に対応
これまで1つだけだった問題報告が、複数の問題を報告してくれるようになりました。エラー表示もコードの内容と指摘箇所を同時に表示されるようになり、非常にわかりやすくなりました。
$ rbs -I t.rbs validate W, [2023-12-31T13:29:27.893150 #32732] WARN -- rbs: t.rbs:2:11...2:25: `void` type is only allowed in return type or generics parameter (RBS::WillSyntaxError) def foo: (void) -> void ^^^^^^^^^^^^^^ E, [2023-12-31T13:29:27.904694 #32732] ERROR -- rbs: t.rbs:6:17...6:25: Could not find Integger (RBS::NoTypeFoundError) def bar: () -> Integger ^^^^^^^^
逆に--fail-fast
というオプションも追加されました。
より厳密なチェック
void
、instance
、self
、class
の使用がより厳密になり、想定しない使われ方をしている場合にwarningが出るようになりました。
例として以下のようなRBSが含まれている場合、指摘されるようになります。
class Sample
# voidは返り値でのみ使える
def f1: (void) -> untyped
# instanceが使えるのはmethodだけ
CONST: instance
end
この挙動は、はじめはerrorになるように実装されていたのですが、gem_
このwarningは将来的にRBSのSyntax Errorとなる予定なので、警告を見つけたら報告するか修正することを強くオススメします。
また--exit-error-on-syntax-error
というオプションをつけることで、warningがerrorになるように挙動を変更することもできます。もし状況的に問題ないのであれば、こちらのオプションをつけておくと将来的な移行がスムーズになるでしょう。
RBS同士を組み合わせる機能の追加
rbs subtract
コマンドは、RBS同士を組み合わせて新たなRBSを生成するための新コマンドです。RBSの引き算
例として、手書きしたhandwritten.
は最優先にして、prototype等で自動生成したgenerated.
から同じメソッドは削除したい場合は以下のようにします。
$ rbs subtract --write generated.rbs handwritten.rbs
このコマンドをrbs prototypeと組み合わせてrake taskとして登録しておくことで、複数のRBS生成ツールを組み合わせることが容易にできるようになりました。
より詳しくはRubyKaigi 2023での作者の発表を観てください。
プロトタイプ生成機能もより充実
プロトタイプ生成機能もより充実しました。
インスタンス変数もプロトタイプ生成できるように
rbs prototype rb
コマンドは、Rubyファイルを静的に読み込んでRBSのプロトタイプを生成するコマンドです。
これまでの出力に加えて、インスタンス変数・
メタプログラミングによるプロトタイプ生成機能が大幅パワーアップ
rbs prototype runtime
コマンドは、rbs prototype rb
コマンドに似ていますが、一度Rubyコードをrequire
で読み込み、Rubyのメタプログラミングを利用してRBSのプロトタイプを生成する機能です。
便利なオプション機能の追加
- class/
module名の定義だけ欲しい場合のため --outline
オプションが追加されました。 - RBSとしてまだ用意されていないメソッドのみ出力する
--todo
オプションが追加されました。プロトタイプなので、そのままRBS追加の足掛かりにできます。coreのメソッドでも調べてみると意外と実装されていないメソッドもあったりするので、「rbsに何か貢献したい」 といった人のサポートにもなります。 - 解析対象を拡張するため
--autoload
オプションが追加されました。遅延読み込み設定にされているclass/moduleを、あえて読み込むことができます。このオプションは将来的にデフォルトで有効になる予定です。
StructとDataに丁寧に対応
これまでコミュニティーでは、rbs prototype runtime
ではStructとDataのメンバー名に合わせたオススメのRBSを特別に生成することで、参考となるRBS定義を示す機能が追加されました。
多くのバグ修正とパフォーマンスの向上
特定のケースでは100倍性能が向上するなど、パフォーマンスの向上や多くの不具合修正が行われました。このコマンドはまだまだ改善の余地が大きいので今後に期待です。
カジュアルにRBS構文が試せるように
rbs parse
コマンドはRBSのシンタックスチェックをするコマンドです。アップデートによって、直接RBSを与えてシンタックスを確認できるようになりました。また、parseする種類も指定できるようになりました。
RBSの内容をファイルではなく直接書きたいときは-e
オプションをつけることで実現できます。また、メソッドの型だけでいい場合は--method-type
オプションを、型単体だけでいい場合は--type
オプションをあわせてつけられるようになりました。
以下にProc型を試したときの例を示します。どうやらSyntaxを間違っていることがワンライナーで確認できました。間違っている箇所もわかりやすく表示されるようになっています。
$ rbs parse --type -e '^() >> Integer' -e:1:4...1:6: Syntax error: expected a token `pARROW`, token=`>>` (tOPERATOR) (RBS::ParsingError) ^() >> Integer ^^
「結局何が変わったの?」がみつけられるように
rbs diff
という新コマンドは、GitHub等でのプルリクエスト作成時にとくに役立ちます。RBSファイル間の大きな差分を効率的にレビューできるように作られたコマンドです。rbs diff
ではA環境とB環境に読み込まれたRBSのセットから、特定のクラスに関する差分を抽出し、markdownのtableや、diff形式で表示できます。これにより、何が変更され、何が追加または削除されたのかをレビュアーが容易に把握できるようになります。
使い方の例として、time
ライブラリをrequireしたら、どのメソッドがTime
classに追加されるのか一覧をmarkdown形式で出して、出力を掲載しておきます。
$ rbs diff --format markdown --type-name Time --after stdlib/time
before | after |
---|---|
- |
def rfc2822: () -> ::String |
- |
alias rfc822 rfc2822 |
- |
def httpdate: () -> ::String |
- |
def xmlschema: (?::Integer fraction_ |
- |
alias iso8601 xmlschema |
- |
def self. |
- |
def self. |
- |
def self. |
- |
def self. |
- |
alias self. |
- |
def self. |
- |
def self. |
- |
alias self. |
ただ構文の差分を取るのではなく、overloadやclassの継承やmoduleのmixinを解決した後の差分を一覧で確認できるので、rbs subtract
との違いを簡単に説明すると、rbs subtract
が静的な重複記述の除去、rbs diff
は動的な差分の表示を目的にしています。
数多くのRBS定義がアップデート
数え切れないほどのRBS定義が見直され、追加や修正が行われました。
個人的にとくに印象的だったRBS定義の変化は、Object classに定義された数多くのメソッドがまるごとKernel moduleへ移動されたことです。これまではRBSのメソッドに対するドキュメンテーションがrdocとリンクしていたため、RubyではKernelに定義されているメソッドがObjectに定義されていました。しかしながら、このドキュメンテーションの問題も解消されていたため、Rubyの実装に合わせてObjectからKernelへメソッドのRBS定義が移されました。
時を同じくして同じような議論がRuby側でもなされていたようで、rdocと実装でメソッドの定義位置が違うことが初学者に混乱を生んでいました。結論としては、実装と同じようにドキュメントも移動するようになったようです。
- Many Kernel methods are defined as Object methodsmethods · Issue #1475 · ruby/
rbs - Misc #19304: Kernel vs Object documentation - Ruby master - Ruby Issue Tracking System
周辺ライブラリの変化
最後に、周辺ライブラリの変化を取り上げます。
- Steep
-
RBSの生みの親である、Rubyコードの静的型チェッカーです。上記RBSのアップデートに対応しつつ、LSPの機能を活用したアップデートが複数なされています。より使いやすく実践的なツールに進化しています。
- TypeProf
-
RBSを書かなくても型を推論するという野心的なツールですが、RubyKaigi 2023でTypeProfのリブートが発表されました。LSPを主要なユースケースにすえ、大幅なパフォーマンス向上を目指して開発されています。ただし、このリブート版はRuby 3.
3にはバンドルされていません。 - IRB
-
Rubyの標準的なREPLで、最近RBSを利用した補完機能が大きな注目を集めました。型があることで補完が速くなるというおもしろいアプローチをとっています。
- Sorbet
-
StripeとShopifyが力を入れて開発している、もう1つのRubyの型プロジェクトです。
残念ながら筆者がSorbetについてキャッチアップできていないのですが、高速なチェッカー、充実の型ファイル自動生成、カバレッジのグラフ表示と、エコシステムとしてのSorbetはRBSよりもずいぶん先を行っていると考えており、参考実装としてリスペクトしつつ後を追いかけていきたいものです。
- Orthoses
-
手前味噌ですが、RubyKaigi 2022とKaigi on Rails 2023で発表されたRBSの自動生成システムです。基本的な機能は実装済みですが、さらに発展的な機能を盛り込もうとrbs本体に手を伸ばして絶賛開発中です。
今後のRBS
今後は、新たなシンタックスの追加、パフォーマンスの向上、著名ライブラリでのRBS同梱、テストでの活用など、さまざまな方向への発展が期待されています。
Rubyの型は、まだまだ未開拓な部分が多く、これからの発展が非常に楽しみです。