カリー=ハワード同型対応
論理の命題と型、論理の証明とプログラムが一対一に対応するという発見。
プログラマ向けの一言
型を書くこと = 命題を書くこと。コンパイルが通るプログラムを書くこと = 証明を書くこと。 型チェッカーは実は定理の証明チェッカーでもある。
対応表
// 論理 ↔ プログラミング
// ─────────────────────────────────
// 命題 ↔ 型
// 証明 ↔ プログラム(その型の値)
// A ∧ B(かつ) ↔ [A, B](タプル / 直積型)
// A ∨ B(または) ↔ A | B(直和型 / union)
// A → B(ならば) ↔ (a: A) => B(関数)
// ⊤(真) ↔ void / unit(常に作れる値)
// ⊥(偽) ↔ never(作れない値)
// ¬A(否定) ↔ (a: A) => never(A があると矛盾)
// ∀x. P(x) ↔ <T>(x: T) => P<T>(ジェネリクス)
コードで理解する
// --- 「A かつ B ならば A」の証明 ---
// 命題: A ∧ B → A
// 型: ([A, B]) => A
function proof1<A, B>(pair: [A, B]): A {
return pair[0]; // タプルの1つ目を取り出すだけ
}
// この関数が書けた = この命題が証明された
// --- 「A ならば A または B」の証明 ---
// 命題: A → A ∨ B
// 型: (a: A) => A | B
function proof2<A, B>(a: A): A | B {
return a; // A はそのまま A | B の一員
}
// --- 「A → B かつ B → C ならば A → C」の証明 ---
// 命題: (A → B) ∧ (B → C) → (A → C)
// 型: 三段論法 = 関数合成
function proof3<A, B, C>(f: (a: A) => B, g: (b: B) => C): (a: A) => C {
return (a) => g(f(a)); // 合成するだけ
}
// --- never(偽 / ⊥)からは何でも証明できる ---
// 命題: ⊥ → A(爆発原理 / ex falso quodlibet)
function absurd<A>(x: never): A {
return x; // never 型の値は存在しないので、この関数は呼ばれない
}
なぜ重要か
// 1. 型チェッカー = 証明チェッカー
// コンパイルが通る → 型(= 命題)に対する値(= 証明)が存在する → 命題が正しい
// 2. 定理証明系の基盤
// Coq, Agda, Lean は「プログラムを書く = 定理を証明する」をそのまま実現
// カリー=ハワード対応があるからこそ可能
// 3. 型を見れば性質がわかる
// (a: A) => A ← これを満たす関数は恒等関数しかない(パラメトリシティ)
// <A, B>(f: (a: A) => B, xs: A[]) => B[] ← map しかない
// 4. 「型で考える」ことの正当化
// 型設計 = 仕様設計。型が通るプログラムを書く = 仕様を満たす実装を書く。
押さえどころ(カード化候補)
- カリー=ハワード対応とは → 命題=型、証明=プログラム(その型の値)が一対一に対応する。型チェッカーは証明チェッカーでもある
- 論理結合子の対応 → A∧B↔タプル/直積、A∨B↔union/直和、A→B↔関数、⊤↔unit、⊥↔never、¬A↔(a:A)=>never、∀↔ジェネリクス
- 証明=コード例 → A∧B→A はタプルの射影、A→A∨B は包含、三段論法 (A→B)∧(B→C)→(A→C) は関数合成、⊥→A(爆発原理)は never からの関数
- なぜ重要か → 定理証明系(Coq/Agda/Lean)の基盤。型から性質が読める(パラメトリシティ: (a:A)=>A は恒等関数のみ)。型設計=仕様設計