notes mizzy.org

typestateで壊れた状態を書けなくする

確認済み 出典: explorations/2026-06-10-carina-type-system-type-theory/type-system-analysis.md付録 作成/ 更新

Carinaの処理系は「処理が進むと値の型が変わる」typestateパターンを系統的に使っている。狙いは一貫していて、静的段階の内部不変条件を、実装言語の型でコンパイル時に固定することである。

適用例は複数の層に及ぶ。

規約で守る — 「比較の前に正規化する」 DSL綴りの識別子 EnumValue リゾルバ 正規形へ解決 比較・直列化 忘れられる経路が残る — 未正規化のまま比較に届く typestate — 型が違えば届かない 未解決識別子 Unresolved リゾルバ 唯一の構築子 比較・直列化 Resolvedしか受けない Resolved この経路は型が構成できない
不変条件を守る力が、各消費地点の記憶から型の構成可能性へ移る。下段では迂回路が禁止されているのではなく、書き表せない。

「覚えている」に依存しないこと

いずれの例も、対応する不変条件は「各消費地点が処理を忘れないこと」でも守れる。列挙値は比較の前に正規化する、μ型の参照は照合の前に展開する、と規約に書くこともできる。

しかし規約に依存する限り、同じバグクラスに未来の呼び出し側が再び到達できる。正規化を経ない比較経路が残っていたために列挙値が永遠に変更扱いされ続けた事故は、まさにその形で起きた。一箇所の消費地点が忘れたことが、差分という中心機能を静かに壊した。

typestateはこれを型の形に変える。壊れた状態を表す値が構成できないなら、忘れることが可能な地点がそもそも存在しない。「呼び出し側が覚えていること」に依存する不変条件は、型の形に作り直すまで根が残っている、という原則の実装である。

適用先の見当

この原則は未解決の問題にも指し示す先を持つ。代入で解決された値がスキーマで再検証されない問題(carina#3448)に対しては、deferredの穴に期待型を持たせ、穴を埋める唯一のAPIが「その型の検証器だけが生成できる検証済み値」を要求するようにすれば、検査を飛ばした代入が実装言語の型レベルで書けなくなる。検査を置くこと(信頼境界での再検証)と、検査を外せなくすること(typestate)は独立の手当てであり、併用が完成形である。

#typestate #型安全 #設計原則