Solutions · Part 3

第3部 型付き計算

第11–16章 · 18問

第11章

単純型付きラムダ計算——型判断を規則として読む

問題本文
問題1証明項を型導出木へ戻す

Problem

問題

章本文の位置で見る

文脈、型判断、型導出をそれぞれ定義し、同じ判断に複数の導出があり得るか検討せよ。 identity_has_type と constant_has_type のLean証明項を推論規則による導出木へ変換し、各構成子が変数・ 抽象・適用のどの規則に対応するかを注記する。

identity_applied_has_type では適用規則の関数側と引数側で共有される型を全て書き出せ。導出木の 枝が独立ではなく、中間型によって整合させられていることを確認する。

ヒント

文脈は自由変数の型を並べた有限列、型判断は文脈の下で項が型を持つという主張、型導出はその主張を 規則から組み立てた木です。

解答

identity_has_type は、文脈 [A] の添字0へ変数規則を使い、抽象規則で仮定を閉じる導出です。結論は [] ⊢ λ.0 : A ⇒ A です。constant_has_type では [B,A] の添字1が外側の A を指し、二回の抽象で [] ⊢ λ.λ.1 : A ⇒ B ⇒ A を得ます。identity_applied_has_type の適用規則では関数枝が A ⇒ A、引数枝が A を持ち、共有する中間型は A です。同じ判断に、規則の選択や無関係な変換が ある体系なら複数の導出があり得ます。

証明項を読むときは、構成子を推論規則へ、構成子の引数を前提導出へ戻します。圏論的意味論では同じ木を 射の合成へ翻訳できます。

Lean検査済みL464–474
open FormalLab.Foundations.UntypedLambdaCalculus
open FormalLab.TypedComputation.SimplyTypedLambdaCalculus

example (A : SimpleType) : HasType [] identity (A ⇒ A) :=
  .lam (.var .zero)

example (A B : SimpleType) : HasType [] constant (A ⇒ B ⇒ A) :=
  .lam (.lam (.var (.succ .zero)))

example (A : SimpleType) : HasType [] (.app identity identity) (A ⇒ A) :=
  .app (identity_has_type (A ⇒ A)) (identity_has_type A)
問題2型の付く項と付かない項の境界を探る

Problem

問題

章本文の位置で見る

de Bruijn表現で λ f. λ x. f x を定義し、(A ⇒ B) ⇒ A ⇒ B の型導出をLeanで構成せよ。 添字 0 と 1 がどの束縛変数を指すかを各文脈で示し、添字のずれを型エラーから診断する。

次に自己適用 λx. x x へ単純型を割り当てようとし、X = X ⇒ Y という形の型方程式が生じる ことを導け。単にLeanが拒否したと報告せず、有限に生成される単純型では方程式を解けない理由を 構造の大きさから説明すること。

ヒント

内側の文脈では添字0が x : A、添字1が f : A ⇒ B を指します。自己適用では入力型と関数型を同じ変数へ 同時に要求します。

解答

項は λf.λx.f x、de Bruijn表現では .lam (.lam (.app (.var 1) (.var 0))) です。内側で .var 1 : A ⇒ B と .var 0 : A を適用し、二回抽象して (A ⇒ B) ⇒ A ⇒ B を得ます。 一方 λx.x x では、同じ x が適用の関数として X ⇒ Y、引数として X を持つ必要があります。 従って X = X ⇒ Y が必要です。有限木として作る単純型では右辺が左辺を真部分木として含み、型の大きさが |X| = |X| + |Y| + 1 を満たせないため解がありません。

再帰型を許す体系では型方程式を明示的に解けますが、roll/unrollが必要です。同じ項が型付け可能になる条件を、 体系の型形成規則まで含めて述べます。

Lean検査済みL480–491
open FormalLab.Foundations.UntypedLambdaCalculus
open FormalLab.TypedComputation.SimplyTypedLambdaCalculus

def applyTerm : Term :=
  .lam (.lam (.app (.var 1) (.var 0)))

theorem applyTermHasType (A B : SimpleType) :
    HasType [] applyTerm ((A ⇒ B) ⇒ A ⇒ B) :=
  .lam (.lam (.app (.var (.succ .zero)) (.var .zero)))

example (X Y : SimpleType) : X ≠ X ⇒ Y :=
  X.ne_self_arrow Y
問題3型安全性の三つの主張を分ける

Problem

問題

章本文の位置で見る

置換補題、保存定理、進行定理を、それぞれ仮定と結論を省略せずに書け。保存と進行を組み合わせると 何が保証され、停止まではなぜ保証されないかを説明する。強正規化が追加する主張も区別せよ。

最後に [CHU40] の項に型を記す構文と、本章の外在的判断 HasType を比較する。後世の名称だけに 頼らず、項の構文、型検査へ渡る情報、同じ無型項に複数の型を導出できる可能性という観点で表にせよ。 通常の関数型が定数族に対する依存関数型であることまで説明すれば、次の依存型へ接続できる。

ヒント

代入は導出の構造、保存は一段簡約、進行は閉項の現在状態について述べます。

解答

置換補題は Γ,x:A ⊢ t:B と Γ ⊢ s:A から Γ ⊢ t[x:=s]:B を導きます。保存定理は Γ ⊢ t:A と t→t' から Γ ⊢ t':A、進行定理は [] ⊢ t:A なら t が値であるか ∃t',t→t' と述べます。保存と進行を反復すれば、型付き閉項は有限到達した各段階で行き詰まりません。 しかし無限に進み続ける可能性を排除しないため停止は従いません。強正規化は全ての簡約列が有限だと追加します。 外在的判断は無型項と型を別に関係づけ、Church流の型付き構文は項自体に型情報を持たせます。

依存型では通常の関数型を定数族のΠ型として回収できます。外在的・内在的表現の比較は、後の型安全性証明で 証明量が変わる理由にもなります。

Lean検査済みL743–753
variable {Term Ty : Type}

def Preservation (HasType : Term → Ty → Prop) (Step : Term → Term → Prop) : Prop :=
  ∀ ⦃term next type⦄, HasType term type → Step term next → HasType next type

def Progress (HasType : Term → Ty → Prop) (Step : Term → Term → Prop)
    (Value : Term → Prop) : Prop :=
  ∀ ⦃term type⦄, HasType term type → Value term ∨ ∃ next, Step term next

def Stuck (Step : Term → Term → Prop) (Value : Term → Prop) (term : Term) : Prop :=
  ¬Value term ∧ ¬∃ next, Step term next
補足

保存だけでは最初から行き詰まった型付き項を排除できず、進行だけでは一段進んだ後に同じ型判断を再利用できません。 全実行の安全性には、進行で次の一歩を得て、保存でその到達点へ型を運ぶ反復が必要です。言語へ新しい構文を 加えるときは、進行証明の値の形と保存証明の代入場合を別々に監査します。

第12章

弱化・交換・縮約・代入

問題本文
問題1四つの変換を導出木として復元する

Problem

問題

章本文の位置で見る

weakenFront、exchangeFront、contraction、substituteTop の型を、文脈付き型判断の推論規則へ 書き戻してください。各変換で文脈のどの位置が変わり、項中の各de Bruijn添字がどこへ送られるかを 三変数の例で表にします。文脈だけを交換した誤った実装を一つ作り、どの変数の型が変わるか示せば完了です。

ヒント

文脈の変換と、各変数を新しい文脈のどの添字へ送るかを同じ表にします。

解答

弱化は Γ ⊢ t:A から B,Γ ⊢ rename succ t:A を作り、全自由添字を1増やします。先頭交換は A,B,Γ を B,A,Γ へ替え、添字0と1を交換し2以上を保ちます。縮約は同型の二仮定 A,A,Γ を A,Γ へまとめ、添字0と1をともに0へ、2以上を1減らして送ります。先頭代入は A,Γ ⊢ t:B と Γ ⊢ s:A から Γ ⊢ t[0:=s]:B を作ります。文脈だけを交換して項の添字を保つと、 元の添字0が A から B へ変わり、変数規則の結論型が壊れます。

文脈の並べ替えを実装するときは、型の列だけでなく変数の証拠も同じ写像で移します。この原則は再添字付けや ファイブレーションにも現れます。

Lean検査済みL497–500
#check FormalLab.TypedComputation.StructuralRules.weakenFront
#check FormalLab.TypedComputation.StructuralRules.exchangeFront
#check FormalLab.TypedComputation.StructuralRules.contraction
#check FormalLab.TypedComputation.StructuralRules.substituteTop
出力
FormalLab.TypedComputation.StructuralRules.weakenFront {Γ : TypedComputation.SimplyTypedLambdaCalculus.Context}
  {A B : TypedComputation.SimplyTypedLambdaCalculus.SimpleType} (term : TypedComputation.StructuralRules.Tm Γ A) :
  TypedComputation.StructuralRules.Tm (B :: Γ) A
出力
FormalLab.TypedComputation.StructuralRules.exchangeFront {Γ : TypedComputation.SimplyTypedLambdaCalculus.Context}
  {A B C : TypedComputation.SimplyTypedLambdaCalculus.SimpleType}
  (term : TypedComputation.StructuralRules.Tm (A :: B :: Γ) C) : TypedComputation.StructuralRules.Tm (B :: A :: Γ) C
出力
FormalLab.TypedComputation.StructuralRules.contraction {Γ : TypedComputation.SimplyTypedLambdaCalculus.Context}
  {A C : TypedComputation.SimplyTypedLambdaCalculus.SimpleType}
  (term : TypedComputation.StructuralRules.Tm (A :: A :: Γ) C) : TypedComputation.StructuralRules.Tm (A :: Γ) C
出力
FormalLab.TypedComputation.StructuralRules.substituteTop {Γ : TypedComputation.SimplyTypedLambdaCalculus.Context}
  {A B : TypedComputation.SimplyTypedLambdaCalculus.SimpleType} (argument : TypedComputation.StructuralRules.Tm Γ A) :
  TypedComputation.StructuralRules.Tm (A :: Γ) B → TypedComputation.StructuralRules.Tm Γ B
問題2ラムダの下の代入を一段ずつ追跡する

Problem

問題

章本文の位置で見る

外側の自由変数と内側の束縛変数を一つずつ含む項を Tm で構成し、liftSubstitution が各変数を どう送るか計算してください。弱化を除いた素朴な代入なら自由変数がどの束縛子に捕獲されるかを示します。 正しい結果について、代入前後の文脈と型が一致する導出木を再構成し、各再帰呼出しの不変条件を説明します。

ヒント

新しい束縛変数は添字0のままにし、外側から来る置換項だけを弱化します。

解答

外側の自由変数を添字0、ラムダ内の束縛変数を新しい添字0とすると、外側の変数はラムダ下で添字1になります。 liftSubstitution σ は内側の0を .var 0 へ送り、外側の n+1 を weaken (σ n) へ送ります。 たとえば置換項が外側の .var 0 なら、ラムダ下では .var 1 に弱化されます。弱化しない素朴な代入では .var 0 のままになり、新しいラムダに捕獲されます。不変条件は「再帰先の文脈で各像が元と同じ型を持つ」です。

型変数の下で項変数型を持ち上げるSystem Fでも、同じ「新しい0を固定し外側を弱化する」構造を使います。

Lean検査済みL506–514
open FormalLab.TypedComputation.SimplyTypedLambdaCalculus
open FormalLab.TypedComputation.StructuralRules

def outerVariableUnderBinder (A C : SimpleType) : Tm [A] (C ⇒ A) :=
  .lam (.var (.succ .zero))

example {A C : SimpleType} (argument : Tm [] A) :
    substituteTop (B := C ⇒ A) argument (outerVariableUnderBinder A C) =
      .lam (weakenFront (B := C) argument) := rfl
問題3構造規則を制限した体系を比較する

Problem

問題

章本文の位置で見る

交換を許すが弱化と縮約を許さない変数使用規則を設計し、K = λx. λy. x と W = λf. λx. f x x のどちらが型付けできなくなるか調べてください。単に構成子を削るのではなく、 失われる導出を仮定の使用回数から説明します。通常のSTLC、アフィン型、線形型の差を表にします。 最後に各体系で許容される名前変更の型を比較し、制限がどこへ現れるか示せば完了です。

ヒント

各仮定を0回、1回、複数回使えるかで三体系を比較します。

解答

通常のSTLCは弱化と縮約を許すため仮定を0回または複数回使えます。アフィン型は弱化を許して縮約を禁じるため、 各仮定は高々1回です。線形型は両方を禁じ、各仮定をちょうど1回使います。K=λx.λy.x は y を 使わないため線形体系では型付けできませんが、アフィン体系では可能です。W=λf.λx.f x x は x を 二回使うためアフィン・線形の両方で不可能です。交換を許せば順序は変えられますが、使用回数は変えられません。

資源型では構成子を削るだけでなく、文脈分割と使用回数の不変条件を型へ持たせます。第76章の線形論理へ 同じ表を持ち越せます。

第13章

操作的意味論・一段簡約・評価戦略

問題本文
問題1一つの評価列を二つの表現で追う

Problem

問題

章本文の位置で見る

三つ以上の適用を持つ閉項を作り、値呼びの各一段を推論木と step? の計算結果で並べてください。 各段階で appLeft、appRight、beta のどれを使い、他の二規則をなぜ使えないか説明します。 最後の項が値、β正規形、単なる行き詰まりのどれであるかを三定義から判定すれば完了です。

ヒント

値呼びでは関数位置を先に値へし、次に引数位置、最後にβ簡約します。

解答

例として ((λx.x) (λy.y)) ((λz.z) (λw.w)) を取ります。最初は外側の関数位置を appLeft で λy.y へ進めます。次に外側の関数が値なので、引数内部を appRight で λw.w へ進めます。 最後に両方が値なので beta で λw.w を得ます。各段の step? はこの唯一の次項を返します。最終項は ラムダなので値かつβ正規形であり、単なる行き詰まりではありません。

条件分岐や積を追加した評価器でも、優先順位を推論関係の前提と実行関数の分岐順の双方で照合します。

Lean検査済みL520–528
open FormalLab.Foundations.UntypedLambdaCalculus
open FormalLab.TypedComputation.OperationalSemantics

def threeApplications : Term :=
  .app (.app identity identity) constant

example : step? threeApplications = some (.app identity constant) := rfl
example : step? (.app identity constant) = some constant := rfl
example : step? constant = none := rfl
問題2名前呼びと値呼びの停止挙動を分離する

Problem

問題

章本文の位置で見る

第一引数を返す定数関数へ値と omega を異なる順で適用し、二戦略の簡約列を書いてください。 名前呼びだけが弱頭値へ到達する配置を特定し、使われない引数がいつ捨てられるかを示します。 「名前呼びは常に停止する」という誤った一般化に対して、恒等関数と omega から反例も与えます。 二つの結果を、値へ到達するか、有限列の長さ、評価されない部分項という三列の表にすれば完了です。

ヒント

使われない引数の位置へ自己適用 omega を置きます。

解答

K=λx.λy.x、値 v、発散項 Ω とします。項 (K v) Ω は名前呼びなら (λy.v) Ω → v と進み、Ωを評価せず停止します。値呼びでは関数が λy.v になった後に引数Ωを値へ しようとして無限に進みます。一方 (λx.x) Ω は名前呼びでも Ω へ簡約するため発散します。 従って名前呼びが常に停止するわけではなく、未使用引数を捨てられる配置だけで差が出ます。

遅延評価や短絡評価でも、評価されない式が効果を持つと観察結果まで変わります。停止性だけでなく効果の順序も 別の列で比較します。

問題3関係と実行関数の対応を証明する

Problem

問題

章本文の位置で見る

step? t = some u → Step t u を項 t の構造帰納法で証明してください。各match分岐を三つの Step 構成子へ対応づけ、関数位置がラムダのとき appLeft を選ばない理由を valueCannotStep から 説明します。逆向きも証明し、二方向から値呼び一段簡約の決定性を導けば完了です。

ヒント

実行関数の各成功分岐から対応する Step 構成子を作り、逆向きは Step の導出へ帰納します。

解答

健全性は項 t への構造帰納で示します。適用の関数位置が進めば帰納仮定と appLeft、関数が値で引数が 進めば appRight、両方が値のラムダなら beta を使います。関数位置がラムダ値なら valueCannotStep により appLeft の前提は不可能です。完全性は Step t u の導出帰納で、各構成子に対し step? t = some u を計算します。二方向と関数結果の一意性から、二つの関係後続は同じだと分かります。

仕様関係とインタプリタを併置するときは、健全性だけでなく完全性も検査します。片方向だけでは実装が合法な 一歩を見落としていても検出できません。

Lean検査済みL534–572
open FormalLab.Foundations.UntypedLambdaCalculus
open FormalLab.TypedComputation.OperationalSemantics

theorem step?_complete {term next : Term}
    (reduction : FormalLab.TypedComputation.OperationalSemantics.Step term next) :
    step? term = some next := by
  induction term generalizing next with
  | var index => cases reduction
  | lam body => cases reduction
  | app function argument functionIH argumentIH =>
      cases reduction with
      | beta value => cases value; rfl
      | appLeft functionStep =>
          cases function with
          | var index => cases functionStep
          | lam body => exact False.elim (valueCannotStep .lam functionStep)
          | app left right =>
              change (match step? (.app left right) with
                | some function' => some (Term.app function' argument)
                | none => none) = _
              rw [functionIH functionStep]
      | appRight functionValue argumentStep =>
          cases functionValue
          cases argument with
          | var index => cases argumentStep
          | lam body => exact False.elim (valueCannotStep .lam argumentStep)
          | app left right =>
              change (match step? (.app left right) with
                | some argument' => some (Term.app (Term.lam _) argument')
                | none => none) = _
              rw [argumentIH argumentStep]

theorem stepDeterministic {term first second : Term}
    (left : FormalLab.TypedComputation.OperationalSemantics.Step term first)
    (right : FormalLab.TypedComputation.OperationalSemantics.Step term second) : first = second := by
  have leftResult := step?_complete left
  have rightResult := step?_complete right
  rw [leftResult] at rightResult
  exact Option.some.inj rightResult

第14章

再帰型・fold/unfold・型の無限展開

問題本文
問題1再帰リストを一層ずつ型付けする

Problem

問題

章本文の位置で見る

要素型 A に対する本体 1 + A × X を RType で表し、空リストと二要素リストに相当する項を roll で構成してください。各 roll の直前にある項の型を一層展開した式で書き、HasType の 導出木とLean証明を対応づけます。最外の unroll を一段進めた結果が同じ展開型を持つことを preservation で確認すれば完了です。

ヒント

ListF A X = 1 + A × X と置き、再帰リスト型を μX.ListF A X とします。

解答

空リストは展開型 1 + A × μX.(1+A×X) の左注入を作り、roll で再帰型へ戻します。要素 a と尾 xs から 作るconsは右注入 inr ⟨a,xs⟩ を同じ展開型で作り、再び roll します。二要素リストはこの操作を二回重ねます。 最外の unroll (roll body) は一段で body へ進み、保存定理により展開型 1 + A × μX.(1+A×X) を保ちます。

木では一層関手を 1 + A × X × X などへ替えるだけで、roll前の一層と再帰位置の対応を同じ方法で追えます。

Lean検査済みL578–614
open FormalLab.TypedComputation.RecursiveTypes

def listBody (element : RType) : RType :=
  .sum .unit (.product element (.bound 0))

def emptyList (element : RType) : Term :=
  .roll (listBody element) (.inl .unit)

def unitListBody : RType := listBody .unit

def emptyUnitList : Term := emptyList .unit

def prepend (element : RType) (head tail : Term) : Term :=
  .roll (listBody element) (.inr (.pair head tail))

def prependUnit (tail : Term) : Term := prepend .unit .unit tail

def twoUnitList : Term := prependUnit (prependUnit emptyUnitList)

theorem emptyUnitListHasType : HasType emptyUnitList (.mu unitListBody) := by
  exact .roll (.inl .unit)

theorem prependUnitHasType {tail : Term} (typedTail : HasType tail (.mu unitListBody)) :
    HasType (prependUnit tail) (.mu unitListBody) := by
  exact .roll (.inr (.pair .unit typedTail))

theorem twoUnitListHasType : HasType twoUnitList (.mu unitListBody) := by
  exact prependUnitHasType (prependUnitHasType emptyUnitListHasType)

example : HasType zero recursiveNat := zero_typed
example : HasType (successor zero) recursiveNat := one_typed

example : HasType (.inl .unit) (unfoldType natBody) :=
  preservation (.unroll zero_typed) (.cancel (.inl .unit))

example : HasType (.inl .unit) (unfoldType unitListBody) :=
  preservation (.unroll emptyUnitListHasType) (.cancel (.inl .unit))
問題2型置換の変数捕獲を反例から調べる

Problem

問題

章本文の位置で見る

束縛子の下でreplacementをshiftしない誤った置換を定義してください。開いた型変数 .bound 0 を .mu (.bound 1) へ代入し、正しい結果の .bound 1 と誤った結果の .bound 0 がどの位置を指すかを 図示します。両結果が異なることをLeanで証明し、nestedExample の各添字も同じ規則で追跡してください。 一般の置換補題に必要な自由変数の不変条件を述べれば完了です。

ヒント

μ の下では新しい添字0が導入されるため、外から入れる型の自由添字を1増やします。

解答

正しい置換は μ body の下へ入るとき、対象添字を1増やしreplacementを shift 1 0 します。これを省くと、 replacement中の自由な .bound 0 が内側のμに捕獲されます。最小例では、正しい結果 .mu (.bound 1) の添字1は外側を指し続けますが、誤った結果 .mu (.bound 0) は直近のμを指します。 nestedExample の添字も同じく、0は最内、1は一つ外の束縛子を指します。不変条件は、置換前に自由だった 各型変数が置換後も対応する外部束縛を指すことです。

項代入、型代入、再添字付けのいずれでも、束縛子を越えるときの持ち上げを自由変数保存の補題として先に定めます。

Lean検査済みL620–640
open FormalLab.TypedComputation.RecursiveTypes

def naiveSubstitute (target : Nat) (replacement : RType) : RType → RType
  | .unit => .unit
  | .sum left right =>
      .sum (naiveSubstitute target replacement left)
        (naiveSubstitute target replacement right)
  | .product left right =>
      .product (naiveSubstitute target replacement left)
        (naiveSubstitute target replacement right)
  | .arrow domain codomain =>
      .arrow (naiveSubstitute target replacement domain)
        (naiveSubstitute target replacement codomain)
  | .bound index => if index = target then replacement else .bound index
  | .mu body => .mu (naiveSubstitute (target + 1) replacement body)

example :
    naiveSubstitute 0 (.bound 0) (.mu (.bound 1)) = .mu (.bound 0) := rfl

example :
    substitute 0 (.bound 0) (.mu (.bound 1)) = .mu (.bound 1) := rfl
問題3isoとequiの型付け導出を翻訳する

Problem

問題

章本文の位置で見る

recursiveNat の零と後者について、明示的な roll を持つiso-recursive導出を書いてください。次に TypeEquiv.unfold と型変換規則を仮定したequi-recursive導出から roll を除きます。項の実行時構文、 型同値を調べる負担、型保存証明の各差を比較し、二体系を単なる表記違いと呼べない理由を説明します。

ヒント

iso-recursive型では型の折り畳みを項構文に、equi-recursive型では型同値判断に置きます。

解答

recursiveNat = μX.(1+X) とすると、零は roll (inl unit)、後者は roll (inr n) です。iso体系では 導入・除去に roll と unroll が明示され、その簡約が実行時構文に現れます。equi体系では μX.(1+X) ≡ 1+μX.(1+X) を TypeEquiv.unfold で認め、型変換規則により注入項を再帰型として扱います。 項は短くなりますが、型検査器は再帰的な型同値を決定する必要があります。保存証明も、isoではroll/unroll規則、 equiでは型同値の閉性を扱うため、単なる表記差ではありません。

抽象データ型やnewtypeでも、変換を実行時に持つか型等式に吸収するかを、検査負担と意味論の両面から比較します。

第15章

保存・進行・型安全性

問題本文
問題1進行証明の全分岐を導出木へ戻す

Problem

問題

章本文の位置で見る

progress の適用の場合を、関数側と引数側の二つの選言から四つの組合せとして列挙してください。 実際に残る三分岐へ appLeft、appRight、beta を割り当て、関数値の標準形補題を使う位置を示します。 各分岐の次項を明記し、評価戦略が変わればどの分岐順が変わるか説明すれば完了です。

ヒント

適用 f a について、まず関数側の進行を場合分けし、関数が値のときだけ引数側へ進みます。

解答

関数側が進めるなら f a → f' a を appLeft で作ります。関数側が値で引数側が進めるなら f a → f a' を appRight で作ります。両方が値なら、関数値の標準形補題から f はラムダであり、 beta により本体へ引数を代入します。形式上の四組合せのうち、関数が進める場合には値ではないため引数側の 選言を調べる必要がなく、残る三分岐だけが評価戦略に沿います。名前呼びなら引数の進行分岐を飛ばしてβへ進みます。

新しい値構成子を追加したら、対応する標準形補題と進行分岐を対で増やします。規則だけ追加して補題を更新しない 不整合を防げます。

問題2外在的な保存定理を復元する

Problem

問題

章本文の位置で見る

第11章の HasType と第13章の無型 Step を使い、保存定理の正確な型を書いてください。βの場合に必要な 代入補題、適用の二場合に必要な帰納仮定を特定します。本章の preservation が短い理由を内在的添字から 説明し、短い証明と弱い主張を混同していないことを両定式化の対応表で示してください。

ヒント

外在的定式化では、簡約前後の無型項と共通の型を明示的に関係づけます。

解答

定理は HasType Γ t A → Step t t' → HasType Γ t' A です。βの場合は Γ,x:B ⊢ body:A と Γ ⊢ arg:B から Γ ⊢ body[x:=arg]:A を得る代入補題が必要です。 関数位置・引数位置の簡約では、それぞれの部分項に対する保存の帰納仮定を適用し、適用規則を再構成します。 本章の内在的な一段関係は、始域と終域が同じ型で添字付けされているため、保存が関係の型そのものに組み込まれます。 証明が短いのは主張が弱いからではなく、不変条件をデータ表現へ移したからです。

内在的構文を選ぶと不可能状態を表現できなくなりますが、変換・等式の依存型が複雑になります。証明量だけでなく APIと帰納原理も比較します。

問題3言語拡張が壊す箇所を診断する

Problem

問題

章本文の位置で見る

真偽値型、真偽値二値、条件分岐を Tm と Step に加える設計を書いてください。真偽値型の標準形補題と 進行証明の新しい場合を列挙し、条件分岐の一規則を意図的に欠かしたときに生じる型付き行き詰まりを 構成します。保存と進行のどちらが失敗したかを判定し、欠けた規則を戻した後に標準形、進行、 非行き詰まりの系を同じ順で再証明できれば完了です。

ヒント

真偽値の構成子と条件分岐の三つの簡約規則を揃え、どれか一つを欠かした反例を作ります。

解答

型に Bool、項に true、false、if c then t else u を加えます。条件式はまず c を進め、 if true then t else u → t、if false then t else u → u とします。標準形補題は、閉じたBool型の値が trueかfalseであることです。if false の規則を欠かすと、両枝が同じ型を持つ閉項 if false then true else false : Bool が値でも進めもしないため、進行が失敗します。型は変化していないので 保存の反例ではありません。規則を戻せば標準形、進行、到達状態の非行き詰まりを順に回復できます。

和・積・例外を加えるときも、型付け規則と値、標準形と簡約規則、保存と進行を同じ順で更新します。

第16章

正規形・弱正規化・強正規化

問題本文
問題1三種類の停止主張を反例で分ける

Problem

問題

章本文の位置で見る

値呼びの値、弱正規化、強正規化を量化子まで展開し、各含意の向きを調べてください。自己ループ omega が失敗させる条件を Acc の一段展開から説明します。非決定的な簡約関係を小さく定義し、 正規形へ至る枝と自己ループする枝を同時に持つ項を作って、弱正規化だが強正規化でないことを証明します。

ヒント

正規形を持つこと、正規形へ至る経路があること、すべての経路が停止することを別々に量化します。

解答

正規形は後続を持たない項です。弱正規化は正規形へ至る有限列の存在、強正規化はその項から始まる無限列の 不存在です。β簡約を任意の位置で許すと、(λx.y) Ω は外側を先に縮約して y へ至るので弱正規化します。 しかし捨てられる引数Ωだけを縮約し続ける無限列もあるため、強正規化しません。正規形そのものは強正規化し、 強正規化する項は最長の簡約列を辿れば正規形へ至るので弱正規化します。

決定的評価戦略の停止と、非決定的な全簡約関係の強正規化を混同しないよう、関係と戦略を先に固定します。

Lean検査済みL646–657
inductive ChoiceStep : Bool → Bool → Prop where
  | finish : ChoiceStep true false
  | loop : ChoiceStep true true

theorem falseIsNormal : ¬∃ next, ChoiceStep false next := by
  rintro ⟨next, step⟩
  cases step

def infiniteLoop : Nat → Bool := fun _ => true

theorem infiniteLoopSteps : ∀ n, ChoiceStep (infiniteLoop n) (infiniteLoop (n + 1)) :=
  fun _ => .loop
問題2計算可能性述語の関数型場合を読む

Problem

問題

章本文の位置で見る

原子型を強正規化で解釈し、A ⇒ B の計算可能性を全称量化で書いてください。恒等関数と定数関数が 関数型の条件を満たすために必要な仮定を導出し、任意の引数という量化を有限個のテストで置き換えられない 理由を示します。さらに計算可能性から強正規化を得る補題の向きを型ごとに書きます。型帰納と項帰納の どちらを各段階で使い、代入補題がどこで二つを接続するかを表にすれば完了です。

ヒント

関数自身の停止だけでなく、計算可能な引数を計算可能な結果へ送ることを定義に含めます。

解答

基底型では Comp_A(t) を「t : A かつ t は強正規化する」と置きます。関数型では Comp_{A→B}(f) ≔ f : A→B ∧ ∀a, Comp_A(a) → Comp_B(f a) と定めます。単に f の強正規化だけを 要求しても、適用後に現れる計算を制御できません。恒等関数では任意の計算可能な a に対し (λx.x) a → a なので、簡約に対する閉性から結果も計算可能です。

積型なら各射影、和型なら各場合分けについて計算可能性を定めます。型構成子の観察方法が述語の形を決めます。

問題3正規化器から等式判定器までの欠落を埋める

Problem

問題

章本文の位置で見る

二項を正規化して比較する擬似コードを書いてください。停止性、簡約の健全性、標準形の存在、合流性、 構文比較の決定可能性をどこで使うか注記します。強正規化だけを残して合流性を外した抽象的な 簡約系を作り、同じ項が異なる正規形へ至る反例から、判定器の完全性が壊れることを説明してください。

ヒント

停止する正規化手続きだけでは足りません。正しさ、一意性、正規形の構文的比較可能性を列挙します。

解答

型付き項 t,u を正規化し、得た正規形をα同値を除いて比較します。必要なのは、正規化器の停止、出力が入力と 変換可能である健全性、変換可能な項が同じ正規形を持つ完全性です。完全性には合流性と正規形の一意性を使えます。 さらに束縛変数をde Bruijn添字などで表し、正規形の構文等値を決定できなければなりません。これらから t ≡ u であることと正規形の一致が同値になり、判定器が得られます。

型検査器の変換可能性判定でも同じ分解を使います。η規則を加えるなら正規形と合流性を再確認します。