コンテンツにスキップ

付録 a5 他言語からの橋渡し

この付録は、本編で扱う概念を他言語の知識と結ぶための参照集です。前編の本体はRubyだけを前提に進みますが、Java、Kotlin、TypeScript、Go、PHP(Hack/PHPStan)などの経験がある読者には、「あれと同じ話か」と腑に落ちる橋が架けられます。逆に、これらの言語を知らなくても本編の論旨は完結します。ここは「知っていれば嬉しい」おまけです。

前編Part 4(Union)、Part 5(ナローイング)、Part 6(HashShape)、Part 7(部分型)から、それぞれ「詳しくは付録a5」のポインタで来た読者を想定しています。必要な項だけ引いて、本編に戻ってください


a5-1. null安全「ぬるぽ」を型で捕まえる(Part 5関連)

Section titled “a5-1. null安全「ぬるぽ」を型で捕まえる(Part 5関連)”

Javaを書く人には、Part 5のUser | nilは見覚えがあるはずです。find_userが「見つかればUser、なければnil」を返すのは、Javaの「Usernull」と同じ構図で、RubyのnilはJavaのnullに当たります。そしてxnilのままx.nameを呼べば、RubyならNoMethodError、JavaならNullPointerExceptionぬるぽ)という同じ事故が起きます。

ナローイングは、その事故を型のレベルで先回りして捕まえる仕組みです。if x.nil?のelse節で「ここのxはもうnilじゃない」と型を狭めます。Javaでif (x != null) { … }と書いてからフィールドに触る習慣を、型チェッカーが自動で追ってくれるのと同じことです。「nilを含むUnion」を持ち歩き、ガードを通った所でnil剥がす。剥がし切れていない(nilがまだ型に残っている)場所で.nameを呼べば、そこが「ぬるぽが出る場所」です。

NullPointerExceptionを「実行時にたまたま落ちるもの」と捉えていた人にとって、ここは見方が変わる所です。nullは型で表現でき、型で防げるバグだった。これが「null安全(null safety)」と呼ばれる考え方の芯で、KotlinのUser?、TypeScriptのUser | nullも同じ発想です。Rigor/chibirigorではnilをただのUnionのメンバとして持ち、ナローイングで剥がします。特別扱いの構文を足さずにnull安全の入口に立てるわけです。


a5-2. 名前的部分型と構造的部分型(Part 7関連)

Section titled “a5-2. 名前的部分型と構造的部分型(Part 7関連)”

Javaを書く人は「部分型」と聞くと継承を思い浮かべるはずです。class Dog extends AnimalならAnimal a = new Dog();と代入できる、あれです。確かにそれも部分型の一種で、「Dogの箱はAnimalの箱に入る」と言い換えられます(小さい箱 → 大きい箱)。implements(インターフェース実装)も同じく部分型です。

ただし、部分型は継承と実装だけではありません。Part 6で「キーが多いハッシュは、キーが少ないハッシュの部分型」と言ったのを思い出してください。{name:, age:}{name:}の部分型でしたが、そこにはextendsimplementsも継承関係が一切ありません。「構造(持っているキー)が揃っていれば部分型」と形だけで決まります。これを構造的部分型(structural subtyping)と呼びます。Javaは基本「名前で(継承宣言で)決まる」名前的部分型の世界なので、ここは直感とズレやすい所です。GoのインターフェースやTypeScriptのオブジェクト型が構造的部分型である、と言えばピンと来るかもしれません。

まとめると、部分型には「継承で決まる(名前的)」と「構造で決まる(構造的)」の2系統があります。Rigor/chibirigorは両方を「箱に入るか」という一つの判定にまとめて扱います。 Part 7のacceptsはその入口です(名前的と構造的の使い分けの形式は後編Part 2で扱います)。


a5-3. 構造的ハッシュ型の系譜 Hack → PHPStan → Rigor(Part 6関連)

Section titled “a5-3. 構造的ハッシュ型の系譜 Hack → PHPStan → Rigor(Part 6関連)”

「キーと値の型を覚えた構造的なハッシュ型」(本編のHashShape)はRigorの発明ではなく、型チェッカーがdynamicなハッシュを扱うたびに同じ問題にぶつかってきた歴史の産物です。

  • Hack(Facebook):PHPに静的型を足した言語
    • shape('name' => string, 'age' => int)という型を導入し、「キーを明記する代わりに余分は許す(open)」という設計を採りました
    • 当初からoptionsハッシュとの共存を意識した設計です
  • PHPStan / Psalm:PHPのチェッカーが同じ問題にぶつかり、array{name: string, age: int}という表記で同型を導入
    • 語彙はHackを踏襲し、open/closedを明示できるものもあります
  • Rigor:RubyのRBS { name: String, age: Integer }から型を起こし、同じくopenを採用
    • 「少なくとも」で受け取ります

3ツールとも、素朴なjoin(Hash[Symbol, String | Integer]のような幅の広い型)ではキーごとの情報が失われてしまうため、キーを個別に覚える型が必要でした。chibirigorのHashShapeはこの系譜の最小実装です。


a5-4. 無タグのUnionと「タグ付きバリアント」の違い(Part 4関連)

Section titled “a5-4. 無タグのUnionと「タグ付きバリアント」の違い(Part 4関連)”

Part 4で作るUnionInteger | Stringのような無タグのユニオン型)は、参考書の世界ではむしろ珍しい出発点です。『しくみ』は一般のユニオン型を「型システムへの影響が大きすぎる」としてあえて避け(5章の演習でタグ付きのvariantに少し触れる程度)、『TAPL』が持っているのもタグ付きバリアント(どの型かを示すタグを値に付けて区別する型)であって、私たちが作る無タグのUnionとは別物です。

タグ付きバリアントは「中身がどの型かを実行時に区別する札」を持ちますが、Rubyの値はそんな札を持ちません。xIntegerStringかは、is_a?でその場で確かめるしかありません。だからこそRubyを相手にする私たちには、札に頼らない無タグのUnionと、それを場合分けで絞るナローイング(Part 5)が必須になります。


a5-5. 網羅性検査との方向の違い Java/C# のmissing arm(Part 5関連)

Section titled “a5-5. 網羅性検査との方向の違い Java/C# のmissing arm(Part 5関連)”

Part 5で触れた「到達できない枝(unreachable arm)」の報告は、Java、C# の網羅性検査と逆向きの設計です。Java、C# のswitch/pattern matching網羅性を強制し、caseが全パターンを網羅していないと「missing arm(腕が足りない)」としてコンパイラが止めます。Rigor(とchibirigor)は「足りない」は問わず、代わりに「到達できない枝(unreachable arm)」を報告します。

x : Integerなのにif x.is_a?(String)を書いたとき、その枝は絶対に実行されません。そこを見つけて「余分な分岐です」と伝えます。chibirigorでもcheck --unreachableで実際にこの診断を出せます(opt-in。既定は「動くコードに黙る」を守って沈黙。動くworked exampleは付録a1-3x)。

Java / C#Rigor / chibirigor
何を報告するかmissing arm(網羅しない腕)unreachable arm(絶対に通らない腕)
動くコードへの態度書くまで止める動くものには黙る
誰が損をするか「そのパターンは来ない」と知っている開発者「来ないと思っているが実は来る」バグ

これは健全性(全パターンを押さえる)より誤検知の少なさ(動くコードを脅かさない)を優先するRigorの価値観の現れです。

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