コンテンツにスキップ

Rigorハンドブック

Rubyプログラマー向けに書かれた、Rigorの型モデルの解説です。静的型付けの予備知識は前提にしません。最初に読むときは順番どおり通読し、必要なときに章単位で参照してください。

業務でRubyを書いていて、nilへのNoMethodErrorに何度かぶつかったことがあり、次のような疑問を持っている人を想定しています:

  • rigor checkは実際に何を見ているのか?
  • なぜこの式に警告を出したのか — もっとよくあるのは、なぜ自分が想定した式に警告を出さなかったのか?
  • 推論が及ばないとき、.rbファイルに注釈を書き散らさずにどう推論を補強すればよいか?

ハンドブックはこれらの疑問に答えます。一方、正規型仕様を置き換えるものではありません。正規仕様はdocs/type-specification/にあり、ハンドブックの記述と食い違ったときは正規仕様が拘束力を持ちます。

インストール、CLIコマンドリファレンス、設定キー、ベースライン(baseline)、CIといったオペレーショントピックはユーザーマニュアルにあります。型が何を意味するかを理解したいときはハンドブックを、それに対する操作を行うフラグ・キー・コマンドを調べたいときはマニュアルを参照してください。

  1. はじめにrigor checkの実行、診断の読み方、「注釈は書かない」というスタンス。
  2. 日常的に出会う型 — キャリア(carrier)の種類。定数、整数の範囲、リファインメント(refinement、篩型とも)、ユニオン(union、合併型とも)、Dynamic[Top]。「Rigorが見ているもの」を最短距離で把握できます。
  3. ナローイングifcase、述語メソッドが、分岐内で変数の型をどう絞り込むか。
  4. タプルとハッシュシェイプ — Rubyの[a, b, c]リテラルや{key: value}ハッシュが、Rigorが構造を証明できたときに受け取る構造的なキャリア。TypeScriptのPick/Omit/Partial/Required/Readonlyユーティリティ型に対応するシェイプ(shape)射影関数pick_of/omit_of/partial_of/required_of/readonly_of)も収録。
  5. メソッドとブロック — 引数の型付け、戻り値型の推論、ブロックパラメータ、引数個数(arity)。
  6. クラス — インスタンス側とクラス側、selfattr_accessorData.define
  7. RBSとRBS::Extended — 推論で実際の戻り値型を証明できないとき、.rbsファイルや%a{rigor:v1:…}ディレクティブで推論を後押しする方法。
  8. エラーの読み方 — ルールカタログ(call.undefined-methodcall.argument-type-mismatchflow.always-raises、…)、深刻度プロファイル、# rigor:disableによる抑制。
  9. プラグイン — プラグインを書くべきタイミングと、examples/ランディングページへの導線。
  10. Sorbetとの共存 — Sorbetを使用しているプロジェクトから来たユーザー向け: rigor-sorbetアダプタがsig { ... }ブロック、RBIファイル、T.let / T.cast / T.must / T.unsafeアサーションをRBSで書き直さずに型ソースとして読み取ります。
  11. rigor sig-genでRBSを生成する — Rigorの推論結果からRBSを発行する方法、new-file/new-method/tighter-returnの分類モデル、--print/--diff/--writeモード、--paramsポリシーとADR-5のトレードオフ、RSpecを意識した観察。
  12. 軽量HKT(JSON.parseとその仲間たち) — Rigorの脱関数化された高階型エンコーディング (ADR-20、Yallop & White 2014 / fp-tsの形状)。JSON.parse / YAML.safe_loadを裏付ける バンドルされたjson::value登録、symbolize_names: true + permitted_classes: [...]という呼び出しサイトの判別子、 自前のURIオーバーレイを.rbsで書く方法、ボディの文法、 再帰的な直和のためのレデューサの遅延「結び目固め」処理、 そして意識的に持たないもの(条件付きボディなし、 複数引数コンテナはまだなし、プラグインマニフェストへの フックアップなし)を扱う。

付録 — 他の型チェッカーから来た場合

Section titled “付録 — 他の型チェッカーから来た場合”

別のツールで「静的型チェッカー」の概念を身につけた読者向けの、言語間クイックリファレンスです。各ページはRigorの語彙をすでに知っている概念にマッピングします — 型キャリア、ナローイング(narrowing)のプリミティブ、設定の形式、深刻度モデル、抑制 — そして、ふたつのシステムが本質的に異なる選択をしている箇所を示します。

  • TypeScriptから来た場合 — 構造的型付け(structural typing)vs.名前的型付け(nominal typing)+リファインメントの分割、unknown/any/neverTop/Dynamic[Top]/Bot、型ガード ↔ predicate-if-trueディレクティブ、条件型/マップ型にはRigorの対応物がない話。
  • PHPStanから来た場合 — 精神的に最も近いピアツール。同一のリファインメント語彙(non-empty-stringint<min, max>numeric-stringliteral-string)、@phpstan-assert*RBS::Extended、Type-Specifying Extensions ↔ プラグイン、ベースラインdiffワークフロー。
  • mypy / Pyrightから来た場合漸進的型付けgradual typingの類似点、LiteralConstantTypeGuard/TypeIspredicate-if-true/predicate-if-falseProtocol ↔ RBSのinterfaceLiteralStringliteral-string
  • Steepから来た場合 — RubyのもうひとつのRBS駆動の静的型チェッカー。両ツールとも同じ.rbsファイルを読む。各ツールがRBSの上に重ねるレイヤーと、両方を実行したいプロジェクトのための共存パターンを説明する。
  • TypeProfから来た場合 — Rubyの公式型推論ツール。両ツールともアノテーションなしで推論する。このページでは、プログラム全体(whole-program)vs.ローカル解析のトレードオフ、rigor sig-gentypeprof CLIの直接の対応物である理由、そして診断(diagnostics)vs.RBS出力の分割を説明する。

付録 — プロトコルと構造的型付け

Section titled “付録 — プロトコルと構造的型付け”

Pythonからの読者にもSwiftからの読者にも同じく問われる「RigorのProtocolはどこにある?」という問いのための、独立した概念ページです。Rigorでは無関係なふたつのものを意味するこのひとつの語 — インターフェース(RBSの構造的型、Pythonのtyping.Protocolに相当)とプロトコル契約(ADR-28のパススコープの振る舞い契約)— を解きほぐし、適切なほうに手を伸ばせるようにします。

  • プロトコル、インターフェース、構造的型付け — RBSのinterfacetyping.Protocol、推論されるオブジェクトシェイプとケイパビリティロール、そしてそれらすべてがADR-28のプラグイン宣言・パススコープのプロトコル契約とどう違うのか。横並びの「インターフェースvsプロトコル契約」表と「どちらが欲しいのか?」ガイドを収録。

Rigorの語彙と、プログラミング言語の教科書や別の型チェッカーの ドキュメントで見たことがあるかもしれない形式的な型理論の概念とを つなぐ短い橋渡し。「Rigorは型理論の地形のどこに位置するのか」と いう疑問から入ってきたなら読んでほしい。ハンドブック本編は意識的に 理論を抑えめにしている。

  • 型理論との接続 — 型の束、サブタイピング(subtyping)vs漸進的一貫性、名前的vs構造的、 多相性のファミリー(パラメトリック / 部分型(subtype) / アドホック)、 分散、リファインメント / 述語サブタイピング、occurrence typing、 漸進的型付け、エフェクトシステム、健全性(soundness)vs完全性の トレードオフ、そしてRigorが意図的にモデル化していない機能の 短いリスト(HKT、higher-rank、完全な依存型……)— それぞれに 対応するRigorの表面と仕様コーパスへのポインタを添える。
  • リスコフの置換原則 — なぜLSPは、静的にチェックされる言語に対してよりもRubyに対して より(少なくではなく)当てはまる振る舞い的な規律なのか (「Rubyは静的型付けではないからLSPは任意だ」という主張は原則を 逆さまにとらえている)、Rigorのロバストネス原則(厳密な戻り値、寛容な パラメータ)がどのようにして、置換可能性の証明ではなくRuby採用の エルゴノミクスからLSPのシグネチャ規則(共変な戻り値、反変な パラメータ)を再導出するのか、なぜその収束がRigorのデフォルトを ダックタイピング(duck typing)やSOLIDの「L」と喧嘩させないのか、そしてどの 振る舞い的部分型の義務(クロス階層のオーバーライド互換性、例外規則、 契約による設計、履歴制約)をRigorが静的に強制しないのか。この ページに限り、「LSP」は言語サーバーではなくリスコフを意味する。

各章は理論を最小限に抑え、例を多く載せています。すべての例はMRIで素のまま動く現実のRubyコードであり、その周りの解説文はrigor checkがそのコードに対して言うであろう内容です。

スニペット中にassert_type(...)行が現れたら、それはRigorの内部観察用ヘルパーであり、実行時チェックではありません。プログラム上のその位置で推論された型を固定し、解説文と実際の解析器出力を比較できるようにするためのものです。dump_type(...)も同じ趣旨ですが、不一致でも失敗せずに通知を出力します。

スニペットの記法:

n = 1 + 2
assert_type("Constant<3>", n) # Rigor がリテラル和をたたみ込む

これは「assert_type呼び出しの位置で、Rigorのnに対する推論がConstant<3>、つまりリテラル値3を持つType::Constantキャリアになっている」という意味です。

用語の約束 ── 「interface」: Rubyにはinterfaceキーワードがないため、ほとんどの読者はこの言葉の意味を別の言語から持ち込みます。そしてJava/PHPの名前的な意味(クラスがimplementsを宣言する)が支配的です。RBSのinterfaceはその逆 ── 構造的であり、GoのinterfaceやPythonのProtocolのように、メソッドを持っていることだけで満たされ、宣言は不要です。誤読を避けるため、どの章でも初出時にはこの言葉を「構造的インターフェース」または「RBSインターフェース」と限定し、素の「interface」を使わないでください。プロトコルと構造的型付けの付録が正規の解説です。

ある章でより形式的な文書に言及するとき、リンクはハンドブックを離れて拘束力のある仕様コーパスやADRに移ります:

ハンドブックは数時間で通読できることを目指しています。短く保つために:

  • Rubyそのものの入門はしませんdefclass、ブロック、モジュール、attr_*、正規表現、RBSの基礎などはすべて前提知識とします。
  • 全エッジケースは扱いません。エッジケースは仕様コーパスにあります。
  • 内部契約(エンジン表面、型オブジェクトの公開API)には踏み込みません。それらはdocs/internal-spec/にあります。
  • プラグインの作成にも踏み込みません。これはexamples/の役割で、第9章は1ページの導線にとどめています。

ハンドブックで説明していないトピックが出てきても、関連する仕様文書はワンクリックで開けます。

© 2026 TypedDuck. Licensed under CC BY-SA 4.0.