h5i

第 6 章

実例で追う

本章では、いくつかの小さな項に対してアルゴリズム 5.1 を手で実行し、各実行を型付け規則に対応づける。例は、アルゴリズムの仕事を一つずつ見せるように選んであり、導出を組み立てること、誤った証明を捕まえること、仮定を使うこと、全称命題を具体化すること、型の内部で計算することである。以下のトレースはどれも、プレイグラウンドでカーネル上に再現できる。

定理 TT の証明 tt を検証するために、カーネルは Check(⋅,t,T)\Check(\cdot, t, T) を呼び、これが Infer(⋅,t)\Infer(\cdot, t) を呼んでその結果を TT と比較する。トレースには、Infer\Infer が行う再帰呼び出しと、各呼び出しが対応する型付け規則を示す。ノードを開いたり閉じたりして、再帰を追ってほしい。

恒等証明

Γ1=P:Sort 0\Gamma_1 = P : \Sort{0}、Γ2=Γ1,h:P\Gamma_2 = \Gamma_1, h : P とおく。第 1 章 の冒頭の例の導出は、整形式性の前提を省くと次のようになる。

⋅⊢Sort 0:Sort 1;(T-Sort)Γ1⊢P:Sort 0;(T-Var)Γ2⊢h:P;(T-Var)Γ1⊢λ(h:P). h:P→P;(T-Lam)⋅⊢λ(P:Sort 0). λ(h:P). h:Π(P:Sort 0). P→P;(T-Lam)\dfrac{ \dfrac{}{\cdot \vdash \Sort{0} : \Sort{1}}\rulename{T-Sort} \qquad \dfrac{ \dfrac{}{\Gamma_1 \vdash P : \Sort{0}}\rulename{T-Var} \qquad \dfrac{}{\Gamma_2 \vdash h : P}\rulename{T-Var} }{\Gamma_1 \vdash \tLam{h}{P}{h} : P \to P}\rulename{T-Lam} }{\cdot \vdash \tLam{P}{\Sort{0}}{\tLam{h}{P}{h}} : \tPi{P}{\Sort{0}}{P \to P}}\rulename{T-Lam}

アルゴリズムは、同じ導出を下から上へ組み立てる。呼び出し Infer(⋅,λ(P:Sort 0). λ(h:P). h)\Infer(\cdot, \tLam{P}{\Sort{0}}{\tLam{h}{P}{h}}) は次のように進む。

  • Infer(⋅,  λ(P:Sort 0). λ(h:P). h)\Infer(\cdot,\; \tLam{P}{\Sort{0}}{\tLam{h}{P}{h}}) T-Lam
    • InferSort(⋅,  Sort 0)\InferSort(\cdot,\; \Sort{0}): Infer\Infer が Sort 1\Sort{1} を返すので、注釈は型である T-Sort
    • 文脈が空なので、Freshen\Freshen は PP をそのままにする
    • Infer(Γ1,  λ(h:P). h)\Infer(\Gamma_1,\; \tLam{h}{P}{h}) T-Lam
      • InferSort(Γ1,  P)\InferSort(\Gamma_1,\; P): Infer\Infer が PP を引いて Sort 0\Sort{0} を返す T-Var
      • Freshen\Freshen は hh をそのままにする
      • Infer(Γ2,  h)\Infer(\Gamma_2,\; h) は hh を引いて PP を返す T-Var
      • Π(h:P). P\tPi{h}{P}{P} を返す
    • Π(P:Sort 0). Π(h:P). P\tPi{P}{\Sort{0}}{\tPi{h}{P}{P}} を返す

型を返す各呼び出しは、導出木の一つのノードに対応し、計算を使う呼び出しは一つもない。最後に Check\Check が、結果を宣言された型 Π(P:Sort 0). P→P\tPi{P}{\Sort{0}}{P \to P} と比較する。P→PP \to P は Π(_:P). P\tPi{\_}{P}{P} の略記であり、hh も _\_ も本体には現れないので、両者の違いは束縛変数一つの名前だけである。したがって IsDefEq\IsDefEq は成功し、証明は受理される。

同じ実行を、カーネル自身に記録させたものが次であり、呼び出しビューは上のリストと、証明木ビューは導出と一致する。

# 同じ証明を、完全な項として与える。カーネルはその型を推論し、
# ゴールと比較する。
goal forall (P : Sort 0), P -> P
exact fun (P : Sort 0) => fun (h : P) => h
qed

誤った証明

利用者が、t=λ(P:Sort 0). λ(h:P). Pt = \tLam{P}{\Sort{0}}{\tLam{h}{P}{P}} は Π(P:Sort 0). P→P\tPi{P}{\Sort{0}}{P \to P} を証明すると主張したとしよう。本体は証明 hh ではなく命題 PP を返している。カーネルは Check(⋅,t,Π(P:Sort 0). P→P)\Check(\cdot, t, \tPi{P}{\Sort{0}}{P \to P}) を呼び、まず Infer\Infer が走る。呼び出しは上と同じだが、最後の Infer(Γ2,P)\Infer(\Gamma_2, P) だけが Sort 0\Sort{0} を返す。つまり Infer\Infer は成功し、結果は

Infer(⋅,t)  =  Π(P:Sort 0). Π(h:P). Sort 0.\Infer(\cdot, t) \;=\; \tPi{P}{\Sort{0}}{\tPi{h}{P}{\Sort{0}}}.

となる。項 tt は正しく型付けされるが、主張された命題の証明にはなっていない。Check\Check が二つの型を比較しても、どちらもこれ以上計算できず、いちばん内側の本体が片方では Sort 0\Sort{0}、もう片方では PP なので、束縛変数を改名しても一致しない。そのため IsDefEq\IsDefEq は失敗し、Check\Check は型の不一致を報告する。(第 11 章 では、IsDefEq\IsDefEq が使うアルゴリズムでこの比較を一歩ずつ再現する。)

この誤りが見つかったのは、利用者が証明すべき命題を宣言したからにほかならない。Infer\Infer だけを走らせるカーネルなら、tt を受理し、黙って別の型を割り当てていただろう。どのスクリプトでも goal が先頭に来るのはこのためであり、qed がそれと照合するのもこのためである。

goal forall (P : Sort 0), P -> P
exact fun (P : Sort 0) => fun (h : P) => P
qed

仮定を使う: モーダス・ポネンス

ここまでの例では関数を一度も適用していないので、次の検査を考えよう。

t=λ(P:Sort 0). λ(Q:Sort 0). λ(f:P→Q). λ(p:P). f  pagainstΠ(P:Sort 0). Π(Q:Sort 0). (P→Q)→P→Q,t = \tLam{P}{\Sort{0}}{\tLam{Q}{\Sort{0}}{\tLam{f}{P \to Q}{\tLam{p}{P}{f\;p}}}} \quad\text{against}\quad \tPi{P}{\Sort{0}}{\tPi{Q}{\Sort{0}}{(P \to Q) \to P \to Q}},

この型は「PP ならば QQ であり、PP が成り立つなら、QQ が成り立つ」と述べている。四つの λ\lambda が、これまでと同じように文脈を Γ=P:Sort 0,  Q:Sort 0,  f:P→Q,  p:P\Gamma = P : \Sort{0},\; Q : \Sort{0},\; f : P \to Q,\; p : P まで拡張し、本体 f  pf\;p は Infer\Infer の適用のケースに達する。

  • Infer(Γ,  f  p)\Infer(\Gamma,\; f\;p) T-App
    • Infer(Γ,  f)\Infer(\Gamma,\; f) は P→QP \to Q、すなわち Π(_:P). Q\tPi{\_}{P}{Q} を返す。Whnf\Whnf はこれを変えず、これは Π\Pi である T-Var
    • Check(Γ,  p,  P)\Check(\Gamma,\; p,\; P) は成功する。p:Pp : P が Γ\Gamma にあるからである T-Var, T-Conv
    • Q[_↦p]=Q\subst{Q}{\_}{p} = Q を返す。名前 _\_ は QQ に現れないので、代入は何もしない

利用者が代わりに f  ff\;f と書いていたら、二つ目のステップで Check(Γ,f,P)\Check(\Gamma, f, P) が呼ばれ、P→QP \to Q は PP ではないので失敗する。ここでは関数を自分自身に食わせることはできない。下で f p を f f に変えて試してほしい。

goal forall (P : Sort 0), forall (Q : Sort 0), (P -> Q) -> P -> Q
exact fun (P : Sort 0) => fun (Q : Sort 0) => fun (f : P -> Q) => fun (p : P) => f p
qed

全称命題の具体化

依存的な適用では、T-App の代入が実質的な仕事をする。最初の例の証明を id=λ(P:Sort 0). λ(h:P). h\mathit{id} = \tLam{P}{\Sort{0}}{\tLam{h}{P}{h}} とし、Γ=Q:Sort 0\Gamma = Q : \Sort{0} とする。このとき Infer(Γ,id  Q)\Infer(\Gamma, \mathit{id}\;Q) は次のようになる。

  • Infer(Γ,  id  Q)\Infer(\Gamma,\; \mathit{id}\;Q) T-App
    • Infer(Γ,  id)\Infer(\Gamma,\; \mathit{id}) は Π(P:Sort 0). Π(h:P). P\tPi{P}{\Sort{0}}{\tPi{h}{P}{P}} を返す。すでに Π\Pi である T-Lam
    • Check(Γ,  Q,  Sort 0)\Check(\Gamma,\; Q,\; \Sort{0}) は成功する T-Var
    • (Π(h:P). P)[P↦Q]=Π(h:Q). Q\subst{(\tPi{h}{P}{P})}{P}{Q} = \tPi{h}{Q}{Q}、すなわち Q→QQ \to Q を返す

論理的には、これは ∀\forall 除去であり、「どの命題も自分自身を含意する」から「QQ は QQ を含意する」を得ている。結果の型は引数 QQ の値に依存し、依存 Π\Pi 型が表しているのはまさにこのことで、T-App が代入を行う理由でもある。エクスプローラでは、id\mathit{id} は定義された定数で、QQ は文脈から来る。T-App ノードの注記に代入が記録される。

def id : forall (P : Sort 0), P -> P := fun (P : Sort 0) => fun (h : P) => h

変換

第 3 章 の Id=λ(X:Sort 0). X\mathit{Id} = \tLam{X}{\Sort{0}}{X} を思い出そう。核となる体系には定義がないので、Id  P\mathit{Id}\;P は項 (λ(X:Sort 0). X)  P(\tLam{X}{\Sort{0}}{X})\;P の略記である。次の検査を考える。

t=λ(P:Sort 0). λ(h:Id  P). hagainstΠ(P:Sort 0). P→P.t = \tLam{P}{\Sort{0}}{\tLam{h}{\mathit{Id}\;P}{h}} \quad\text{against}\quad \tPi{P}{\Sort{0}}{P \to P}.

Infer\Infer はまず、注釈 Id  P\mathit{Id}\;P が型であることを確かめ、InferSort(Γ1,Id  P)\InferSort(\Gamma_1, \mathit{Id}\;P) は Infer\Infer の適用のケースに達する。関数 λ(X:Sort 0). X\tLam{X}{\Sort{0}}{X} の型は Π(X:Sort 0). Sort 0\tPi{X}{\Sort{0}}{\Sort{0}} で、引数 PP は Check(Γ1,P,Sort 0)\Check(\Gamma_1, P, \Sort{0}) を通り、結果は Sort 0[X↦P]=Sort 0\subst{\Sort{0}}{X}{P} = \Sort{0} である。したがって Id  P\mathit{Id}\;P は型であり、しかも命題である。本体 hh の型は Id  P\mathit{Id}\;P なので、

Infer(⋅,t)  =  Π(P:Sort 0). Π(h:Id  P). Id  P.\Infer(\cdot, t) \;=\; \tPi{P}{\Sort{0}}{\tPi{h}{\mathit{Id}\;P}{\mathit{Id}\;P}}.

となる。束縛変数を改名するだけでは、これは Π(P:Sort 0). P→P\tPi{P}{\Sort{0}}{P \to P} にならないが、IsDefEq\IsDefEq は比較の前に両辺を計算する。Id  P→βP\mathit{Id}\;P \betared P なので、推論された型は Π(P:Sort 0). Π(h:P). P\tPi{P}{\Sort{0}}{\tPi{h}{P}{P}} になり、最初の例と同じく比較は成功する。型付け規則で言えば、このステップは導出の根における T-Conv の一回の使用である。

⋅⊢t:Π(P:Sort 0). Id  P→Id  P⋅⊢Π(P:Sort 0). P→P:Sort 0Π(P:Sort 0). Id  P→Id  P≡Π(P:Sort 0). P→P⋅⊢t:Π(P:Sort 0). P→P;(T-Conv)\dfrac{ \cdot \vdash t : \tPi{P}{\Sort{0}}{\mathit{Id}\;P \to \mathit{Id}\;P} \qquad \cdot \vdash \tPi{P}{\Sort{0}}{P \to P} : \Sort{0} \qquad \tPi{P}{\Sort{0}}{\mathit{Id}\;P \to \mathit{Id}\;P} \conv \tPi{P}{\Sort{0}}{P \to P} }{\cdot \vdash t : \tPi{P}{\Sort{0}}{P \to P}}\rulename{T-Conv}

アルゴリズムは、T-Conv をどこで適用するかを決して決めない。第 5 章 で述べたとおり、変換は IsDefEq\IsDefEq の呼び出しの中に隠れており、二つの型が比較される場所でしか走らない。

ライブの導出を見ると、計算がどこで起きるかが分かる。注釈 Id  P\mathit{Id}\;P は T-App で検査され、推論された型には、Check\Check がゴールと比較するまで Id  P\mathit{Id}\;P が残ったままである。

# h の注釈は (fun (X : Sort 0) => X) P であり、P に計算される。
goal forall (P : Sort 0), P -> P
exact fun (P : Sort 0) => fun (h : (fun (X : Sort 0) => X) P) => h
qed

演習

上の例のように Infer\Infer を手で走らせてみると、アルゴリズムの理解を確かめるよい練習になり、どれもプレイグラウンドで試せる。

Π(P:Sort 0). Π(Q:Sort 0). P→Q→P\tPi{P}{\Sort{0}}{\tPi{Q}{\Sort{0}}{P \to Q \to P}} を証明せよ。

解答

一つの答えは λ(P:Sort 0). λ(Q:Sort 0). λ(p:P). λ(q:Q). p\tLam{P}{\Sort{0}}{\tLam{Q}{\Sort{0}}{\tLam{p}{P}{\tLam{q}{Q}{p}}}} である。この証明は qq を一度も使わないが、体系は仮定を使わずに済ませることを許している。

含意が推移的であることを証明せよ: Π(P:Sort 0). Π(Q:Sort 0). Π(R:Sort 0). (P→Q)→(Q→R)→P→R\tPi{P}{\Sort{0}}{\tPi{Q}{\Sort{0}}{\tPi{R}{\Sort{0}}{(P \to Q) \to (Q \to R) \to P \to R}}}。

解答

一つの答えは λ(f:P→Q). λ(g:Q→R). λ(p:P). g  (f  p)\tLam{f}{P \to Q}{\tLam{g}{Q \to R}{\tLam{p}{P}{g\;(f\;p)}}} で終わり、適用のケースを二回使う。まず f  p:Qf\;p : Q、次に g  (f  p):Rg\;(f\;p) : R である。

goal forall (P : Sort 0), forall (Q : Sort 0), forall (R : Sort 0), (P -> Q) -> (Q -> R) -> P -> R
exact fun (P : Sort 0) => fun (Q : Sort 0) => fun (R : Sort 0) => fun (f : P -> Q) => fun (g : Q -> R) => fun (p : P) => g (f p)
qed

λ(P:Sort 0). λ(h:P). h  h\tLam{P}{\Sort{0}}{\tLam{h}{P}{h\;h}} が拒否される理由と、アルゴリズム 5.1 のどの行で拒否されるかを説明せよ。

解答

文脈 P:Sort 0,h:PP : \Sort{0}, h : P において、本体 h  hh\;h は適用のケースに達する。Infer\Infer は関数 hh に対して PP を返すが、Whnf(P)=P\Whnf(P) = P は変数であって Π\Pi ではないので、アルゴリズムは else fail "not a function" の行で失敗する。PP の証明は適用できるものではない。Rust でのメッセージは application of non-function である。

覚えておくこと

  • Infer\Infer の実行は導出木を映し、値を返す各呼び出しが一つのノードである。
  • Infer\Infer は、宣言された定理を証明していない項に対しても成功しうるが、それを捕まえるのは、宣言された型に対する Check\Check である。
  • T-App は、仮定が使われる場所(モーダス・ポネンス)であり、全称命題が具体化される場所(Π\Pi の本体への代入)でもある。
  • 型の内部の計算はすべて IsDefEq\IsDefEq が扱い、アルゴリズムは T-Conv をどこで適用するかを決して選ばない。