※ 自分用にLLMでまとめた記事になります。
==
Rubyの型付けの変遷とRBS入門:今から型安全なRubyコードを書くために
はじめに
Rubyは動的型付け言語として知られていますが、近年、型安全性を高めるための取り組みが活発になっています。本記事では、Rubyの型付けの歴史的な変遷を振り返り、特に**RBS(Ruby Signature)**に焦点を当てて、実践的な導入方法まで解説します。
Rubyの型付けの変遷
1. 初期のRuby(1995年〜)
Rubyは1995年にまつもとゆきひろ氏によって開発され、動的型付け言語として設計されました。この時期の特徴:
- 実行時型チェック:型の不一致は実行時にのみ検出される
- ダックタイピング:オブジェクトの型よりもメソッドの存在を重視
- メタプログラミング:実行時にクラスやメソッドを動的に変更可能
柔軟性と開発速度を重視する思想のもと、型注釈は不要でした。
2. 型チェッカーの登場(2010年代後半〜)
大規模プロジェクトでの型安全性のニーズから、コミュニティによる型チェックツールが登場しました。
Sorbet(Stripe開発)
2019年にStripe社が公開した型チェッカーです:
- **独自の型定義言語RBI(Ruby Interface)**を使用
- インライン型アノテーション(
# typed: strictなど)を使用 - 段階的な型付けが可能(
# typed: false→# typed: true→# typed: strict) - 実行時型チェックもサポート
Sorbetは独自のアプローチを採用し、コード内に直接型情報を記述します。
Steep(ソウタロ氏開発)
- RBSベースの型チェッカー
- Sorbetとは異なり、型情報を外部ファイル(.rbs)に記述
- Rubyの思想により近いアプローチ
- TypeScriptにインスパイアされた設計
3. RBS(Ruby Signature)の標準化(2020年〜)
**Ruby 3.0(2020年12月リリース)**でRBSが標準ライブラリとして導入されました:
- 言語標準:Ruby本体に含まれるため、長期的なサポートが期待できる
- 非侵入的:ソースコードを変更せず、別ファイル(.rbs)に型情報を記述
- 段階的導入:既存コードを壊さずに導入可能
RBSの導入により、Rubyにおける型付けが公式にサポートされるようになりました。
RBSとは
RBS(Ruby Signature)は、Rubyの型情報を記述するための言語です。.rbs拡張子のファイルに型定義を記述します。
RBSの基本構文
以下は、RBS公式リポジトリのREADME.mdから引用したチャットアプリの例です:
module ChatApp
VERSION: String
class User
attr_reader login: String
attr_reader email: String
def initialize: (login: String, email: String) -> void
end
class Bot
attr_reader name: String
attr_reader email: String
attr_reader owner: User
def initialize: (name: String, owner: User) -> void
end
class Message
attr_reader id: String
attr_reader string: String
attr_reader from: User | Bot # `|` means union types: `#from` can be `User` or `Bot`
attr_reader reply_to: Message? # `?` means optional type: `#reply_to` can be `nil`
def initialize: (from: User | Bot, string: String) -> void
def reply: (from: User | Bot, string: String) -> Message
end
class Channel
attr_reader name: String
attr_reader messages: Array[Message]
attr_reader users: Array[User]
attr_reader bots: Array[Bot]
def initialize: (name: String) -> void
def each_member: () { (User | Bot) -> void } -> void # `{` and `}` means block.
| () -> Enumerator[User | Bot, void] # Method can be overloaded.
end
end
RBSファイルの配置
プロジェクト構造の例:
my_project/
├── lib/
│ ├── user.rb
│ └── product.rb
└── sig/
├── user.rbs
└── product.rbs
sig/ディレクトリに配置するのが一般的です。
RBSの主要機能(公式ドキュメントより)
以下は、RBS By Exampleから引用した標準ライブラリの例です。
1. 引数なしメソッド
class String
def empty?: () -> bool
end
2. 単一引数メソッド
class String
def include?: (String) -> bool
end
3. 可変長引数メソッド
class String
def end_with?: (*String) -> bool
end
4. オプショナル引数
class String
def ljust: (Integer, ?String) -> String
end
5. オーバーロード
class Array[Elem]
def *: (String) -> String
| (Integer) -> Array[Elem]
end
6. ユニオン型
class String
def <<: (String | Integer) -> String
end
7. オプショナル型(nilable)
class Enumerable[Elem]
def first: () -> Elem?
| (Integer) -> Array[Elem]
end
8. キーワード引数
class String
def lines: (?String, ?chomp: bool) -> Array[String]
end
9. クラスメソッド
class Time
def self.now: () -> Time
end
10. ブロック引数
class Array[Elem]
def filter: () { (Elem) -> boolish } -> ::Array[Elem]
| () -> ::Enumerator[Elem, ::Array[Elem]]
end
11. 型変数
class Hash[K, V]
def keys: () -> Array[K]
end
出典:ruby/rbs docs/rbs_by_example.md
RBSを取り込むための実践ガイド
Step 1: 環境の準備
Rubyのバージョン確認
RBSはRuby 3.0以降で標準ライブラリとして含まれています:
ruby -v # Ruby 3.0以上が必要(推奨:3.1以上)
RBSとSteepのインストール
# Gemfile
gem 'rbs' # Ruby 3.0以降は標準ライブラリだが、開発用に追加することもある
gem 'steep' # 型チェックツール
bundle install
Step 2: プロジェクトの初期化
RBSの初期化
bundle exec rbs init
これにより、sig/ディレクトリが作成され、基本的な型定義ファイルが生成されます。
Steepプロジェクトの初期化
Steep公式リポジトリのREADME.mdに従って、steep initを実行します:
bundle exec steep init
これにより、Steepfileが作成されます。以下は公式の例です:
# Steepfile
target :app do
check "lib"
signature "sig"
library "pathname"
end
Step 3: 既存コードへの段階的導入
アプローチ1: 新しいコードから始める
新しく書くコードだけにRBSを適用する方法です。
アプローチ2: 重要なクラスから型付け
エラーの影響が大きいクラスから優先的に型付けします。
Step 4: RBSの自動生成ツール活用
rbs prototype コマンド
RBS公式リポジトリのREADME.mdによると、rbs prototypeコマンドで既存のRubyコードからRBS定義を自動生成できます:
# person.rb
class Person
attr_reader :name
attr_reader :contacts
def initialize(name:)
@name = name
@contacts = []
end
def speak
"I'm #{@name} and I love Ruby!"
end
end
$ rbs prototype rb person.rb
出力例:
class Person
@name: untyped
@contacts: untyped
attr_reader name: untyped
attr_reader contacts: untyped
def initialize: (name: untyped) -> void
def speak: () -> ::String
end
生成されたRBSは概形なので、手動で調整が必要です。
rbs prototypeには3つのオプションがあります:
-
rb- 利用可能なRubyコードから生成 -
rbi- Sorbet RBIから生成 -
runtime- ランタイムAPIから生成
TypeProfの活用
TypeProfはRuby 3.3以降でサポートされています:
gem install typeprof
Step 5: 型チェックの実行
Steepによる型チェック
Steep公式リポジトリのREADME.mdに従って、型チェックを実行します:
# 型チェックの実行
bundle exec steep check
# 対話的な型チェック(ファイル変更を監視)
bundle exec steep watch
CI/CDへの組み込み
.github/workflows/type_check.ymlの例:
name: Type Check
on: [push, pull_request]
jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2
- run: bundle install
- run: bundle exec steep check
Step 6: 段階的な厳格化
Steep公式リポジトリの例を参考に、以下のような段階的な型付けが可能です。
初期段階
全てをuntypedで開始してもOK:
class Calculator
def add: (untyped, untyped) -> untyped
end
徐々に型を絞り込む
# Step 1: 戻り値の型を指定
def add: (untyped, untyped) -> Integer
# Step 2: 引数も型指定
def add: (Integer, Integer) -> Integer
Step 7: IDE統合
VS Code
Steep公式によると、Steep VSCodeプラグインをインストールします:
- VSCode: soutaro/steep-vscode プラグインをインストール
SteepはLanguage Server Protocolを実装しており、LSPをサポートするエディタで使用可能です。
よくある落とし穴とベストプラクティス
1. メタプログラミングとの相性
RBSはメタプログラミングを完全には表現できません。必要に応じてuntypedを使用します。
2. Railsとの統合
RailsプロジェクトでのRBS定義には、rbs-railsなどのツールが利用できます。
3. 外部ライブラリの型定義
標準ライブラリや人気gemの型定義はgem_rbs_collectionで管理されています。
公式READMEに従って、以下のコマンドを実行します:
# rbs_collection.yaml を作成
rbs collection init
# 依存関係を解決し、このリポジトリからRBSファイルをインストール
rbs collection install
rbs_collection.yamlが存在する場合、インストールされたRBSは自動的に読み込まれます。
rbsとsteepコマンドには追加の設定は不要です。
出典:ruby/gem_rbs_collection README.md
4. テストコードとの統合
型チェックとテストを併用することで、より堅牢なコードが書けます。
SorbetとRBSの違い
| 項目 | Sorbet | RBS |
|---|---|---|
| 開発元 | Stripe | Ruby公式 |
| 型定義の場所 | コード内(RBI) | 外部ファイル(.rbs) |
| 標準化 | コミュニティツール | Ruby標準ライブラリ |
| 実行時チェック | あり | なし(Steepは静的解析のみ) |
| 段階的導入 | 可能 | 可能 |
| メタプログラミング | 一部サポート | 限定的 |
まとめ
Rubyの型付けは、SorbetやSteepのようなコミュニティツールから始まり、RBSの標準化により新たな段階に入りました。RBSの主な利点は:
- 非侵入的:既存コードを変更せずに導入可能
- 段階的導入:少しずつ型を付けられる
- 標準化:Ruby本体に含まれるため、長期的なサポートが期待できる
- IDE統合:開発体験の向上
既存のプロジェクトでも、新しいコードから始めて段階的に導入することで、型安全性を高めながら開発を続けることができます。まずはrbs prototypeで現状を把握し、重要な部分から徐々に型を付けていくことをおすすめします。