import FormalLab.Foundation.UntypedLambdaCalculus /-! # 第11章:単純型付きラムダ計算——型判断を規則として読む ラムダ項に型を付けるとは、各項へ後からラベルを貼ることなのでしょうか。それとも、 文脈と規則から導出できる項だけを受理することなのでしょうか。 **単純型付きラムダ計算**(simply typed lambda calculus; STLC)は、非型付きラムダ項へ 型判断を与えます。本章ではChurch流のように変数へ型注釈を埋め込む構文ではなく、同じ 生の項に対して外側から判断を定義します。この選択により「項の構文」と「項を受理する 型付け規則」を分離して観察できます。文脈、変数、適用、抽象の規則から実際の導出木を作り、 最後に進行・保存・強正規化が互いに異なるメタ定理であることまで見通します。 ## 生のラムダ項に型判断を与える 対象言語の型と項を $$ A,B ::= \iota\mid A\Rightarrow B, \qquad t,u ::= x\mid t\,u\mid\lambda x.t $$ で生成し、判断 `Γ ⊢ t : A` を変数・適用・抽象の三規則で帰納的に定めます。このBNF風の 記法は構文の生成規則であり、Lean自身の型宣言ではありません。下ではこの対象言語と判断を Leanの帰納型として別々に形式化します。 -/ namespace FormalLab.TypeTheory.SimplyTypedLambdaCalculus open FormalLab.Foundation.UntypedLambdaCalculus /-! ## 単純型と文脈をデータとして定義する 最小の単純型は、原子型と関数型から帰納的に作れます。`A ⇒ B` は、`A` 型の入力を 受け取り `B` 型の出力を返す関数の型です。ここでは対象言語の型とLeanの関数型を 区別するため、独自の構文木を定義します。 -/ /-- 単純型の構文。 -/ inductive SimpleType where | atom : SimpleType | arrow : SimpleType → SimpleType → SimpleType deriving DecidableEq infixr:60 " ⇒ " => SimpleType.arrow /-- 文脈は、最も近い束縛変数の型を先頭に置く型の列である。 -/ abbrev Context := List SimpleType /-! 文脈 `Γ` は、自由変数について現在仮定している型を記録します。de Bruijn添字 `0` は 先頭、`n + 1` は一つ外側の仮定を参照します。`Lookup Γ n A` は「`Γ` の `n` 番目の 変数は型 `A` を持つ」という判断です。文脈は実行時に関数へ渡す値の列ではなく、導出を 検査するときに利用できる仮定の列です。同じ生の変数 `.var 0` でも、文脈が変われば 割り当てられる型は変わります。 -/ /-- 文脈中のde Bruijn添字へ型を割り当てる導出関係。 -/ inductive Lookup : Context → Nat → SimpleType → Prop where | zero : Lookup (A :: Γ) 0 A | succ : Lookup Γ index A → Lookup (B :: Γ) (index + 1) A /-- 固定した文脈と添字に対する変数の型は一意です。 -/ theorem lookup_unique {Γ : Context} {index : Nat} {A B : SimpleType} (left : Lookup Γ index A) (right : Lookup Γ index B) : A = B := by induction left generalizing B with | zero => cases right rfl | succ left inductionHypothesis => cases right with | succ right => exact inductionHypothesis right /-! ## 型判断 `HasType Γ term A` に対応する型付け判断を `Γ ⊢ term : A` と書きます。これは真偽を計算する関数では なく、次の三規則から有限な導出を構成できるという命題です。 $$ \frac{\Gamma(n)=A}{\Gamma\vdash n:A}\;(\mathsf{var}) \qquad \frac{\Gamma\vdash f:A\Rightarrow B\qquad\Gamma\vdash a:A} {\Gamma\vdash f\,a:B}\;(\mathsf{app}) $$ $$ \frac{A::\Gamma\vdash t:B} {\Gamma\vdash \lambda.t:A\Rightarrow B}\;(\mathsf{lam}). $$ 規則名は証明項の構成子です。したがって型検査の結果だけでなく、なぜその型を持つかという 導出木そのものをLeanの値として構成できます。 -/ /-- 文脈の下で生のラムダ項が単純型を持つ、という型判断。 -/ inductive HasType : Context → Term → SimpleType → Prop where | var : Lookup Γ index A → HasType Γ (.var index) A | app : HasType Γ function (A ⇒ B) → HasType Γ argument A → HasType Γ (.app function argument) B | lam : HasType (A :: Γ) body B → HasType Γ (.lam body) (A ⇒ B) /-! ## 有限単純型は自分自身を真部分として含めない -/ /-- 型構文木の節点数です。 -/ def SimpleType.size : SimpleType → Nat | .atom => 1 | .arrow domain codomain => domain.size + codomain.size + 1 /-- 型は、その型を始域に持つ関数型より真に小さい。 -/ theorem SimpleType.size_lt_self_arrow (A B : SimpleType) : A.size < (A ⇒ B).size := by simp only [SimpleType.size] omega /-- 有限に生成された単純型では方程式 `A = A ⇒ B` は解を持ちません。 -/ theorem SimpleType.ne_self_arrow (A B : SimpleType) : A ≠ A ⇒ B := by intro equality exact (Nat.ne_of_lt (A.size_lt_self_arrow B)) (congrArg SimpleType.size equality) /-! ## 導出を構成する 恒等関数の本体 `0` は、拡張された文脈の先頭にある型 `A` を持ちます。その一段の 変数導出を `lam` 規則で閉じれば、空文脈でも `A ⇒ A` が得られます。 -/ theorem identity_has_type (A : SimpleType) : HasType [] identity (A ⇒ A) := .lam (.var .zero) theorem constant_has_type (A B : SimpleType) : HasType [] constant (A ⇒ B ⇒ A) := .lam (.lam (.var (.succ .zero))) theorem identity_applied_has_type (A : SimpleType) : HasType [] (.app identity identity) (A ⇒ A) := .app (identity_has_type (A ⇒ A)) (identity_has_type A) /-- Curry流では、同じ生の恒等項が異なる単純型の導出を持ちます。 -/ theorem identity_has_distinct_typings : HasType [] identity (.atom ⇒ .atom) ∧ HasType [] identity ((.atom ⇒ .atom) ⇒ (.atom ⇒ .atom)) := ⟨identity_has_type .atom, identity_has_type (.atom ⇒ .atom)⟩ example : (.atom ⇒ .atom) ≠ ((.atom ⇒ .atom) ⇒ (.atom ⇒ .atom)) := by decide /-! `identity_applied_has_type` の導出を下から上へ読むと、結論は `identity identity : A ⇒ A` です。適用規則は、関数位置の恒等項へまず `(A ⇒ A) ⇒ (A ⇒ A)` を要求し、引数位置の 同じ恒等項へ `A ⇒ A` を要求します。同じ生の項が二か所で異なる型を持つのは矛盾では ありません。それぞれ別の導出で型変数 `A` の具体化が異なるからです。 $$ \frac{\vdash \mathsf{id}:(A\Rightarrow A)\Rightarrow(A\Rightarrow A) \qquad \vdash \mathsf{id}:A\Rightarrow A} {\vdash \mathsf{id}\;\mathsf{id}:A\Rightarrow A}. $$ 型は同じ項の全ての部分に独立に付くのではありません。適用規則では、関数の入力型と 引数の型が同じでなければ導出を組み立てられません。また `λ x. x x` の自己適用を 単純型付けしようとすると、同じ `x` に `A` と `A ⇒ B` を同時に要求します。有限な 単純型ではこの方程式を解けません。非型付き計算で書ける項を、型規則が選別しています。 `SimpleType.ne_self_arrow` は「有限な単純型では解けない」という説明を、型構文木の大きさが $A