h5i

Chapter 6

Worked Examples

This chapter runs Algorithm 5.1 by hand on a handful of small terms and relates each run to the typing rules. The examples are chosen to show the algorithm doing each of its jobs: building a derivation, catching a wrong proof, using a hypothesis, instantiating a universal statement, and computing inside a type. Every trace below can be reproduced in the kernel with the playgrounds.

To verify a proof tt of a theorem TT, the kernel calls Check(⋅,t,T)\Check(\cdot, t, T), which calls Infer(⋅,t)\Infer(\cdot, t) and compares the result with TT. The traces show the recursive calls that Infer\Infer makes, with the typing rule each call corresponds to. Expand or collapse the nodes to follow the recursion.

The identity proof

Let Γ1=P:Sort 0\Gamma_1 = P : \Sort{0} and Γ2=Γ1,h:P\Gamma_2 = \Gamma_1, h : P. The derivation of the opening example of Chapter 1, with the well-formedness premises omitted, is

⋅⊢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}

The algorithm builds the same derivation from the bottom up. The call Infer(⋅,λ(P:Sort 0). λ(h:P). h)\Infer(\cdot, \tLam{P}{\Sort{0}}{\tLam{h}{P}{h}}) proceeds as follows.

  • 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 returns Sort 1\Sort{1}, so the annotation is a type T-Sort
    • Freshen\Freshen leaves PP unchanged, since the context is empty
    • Infer(Γ1,  λ(h:P). h)\Infer(\Gamma_1,\; \tLam{h}{P}{h}) T-Lam
      • InferSort(Γ1,  P)\InferSort(\Gamma_1,\; P): Infer\Infer looks up PP and returns Sort 0\Sort{0} T-Var
      • Freshen\Freshen leaves hh unchanged
      • Infer(Γ2,  h)\Infer(\Gamma_2,\; h) looks up hh and returns PP T-Var
      • returns Π(h:P). P\tPi{h}{P}{P}
    • returns Π(P:Sort 0). Π(h:P). P\tPi{P}{\Sort{0}}{\tPi{h}{P}{P}}

Each call that returns a type corresponds to one node of the derivation tree, and no call uses computation. Finally, Check\Check compares the result with the stated type Π(P:Sort 0). P→P\tPi{P}{\Sort{0}}{P \to P}. The two differ only in the name of one bound variable, because P→PP \to P abbreviates Π(_:P). P\tPi{\_}{P}{P} and neither hh nor _\_ occurs in the body. So IsDefEq\IsDefEq succeeds and the proof is accepted.

The same run, recorded by the kernel itself. The call view matches the list above; the proof-tree view matches the derivation.

# The same proof, given as a complete term. The kernel infers its type
# and compares it with the goal.
goal forall (P : Sort 0), P -> P
exact fun (P : Sort 0) => fun (h : P) => h
qed

A wrong proof

Suppose a user claims that t=λ(P:Sort 0). λ(h:P). Pt = \tLam{P}{\Sort{0}}{\tLam{h}{P}{P}} proves Π(P:Sort 0). P→P\tPi{P}{\Sort{0}}{P \to P}. The body returns the proposition PP instead of the proof hh. The kernel calls Check(⋅,t,Π(P:Sort 0). P→P)\Check(\cdot, t, \tPi{P}{\Sort{0}}{P \to P}), which first calls Infer\Infer. The calls are the same as above, except that the last one, Infer(Γ2,P)\Infer(\Gamma_2, P), returns Sort 0\Sort{0}. So Infer\Infer succeeds, with

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

The term tt is well typed, but it does not prove the claimed proposition. When Check\Check compares the two types, neither reduces further, and renaming bound variables cannot make them equal: the innermost body is Sort 0\Sort{0} on one side and PP on the other. Thus IsDefEq\IsDefEq fails, and Check\Check reports a type mismatch. (Chapter 11 replays this comparison step by step with the algorithm that IsDefEq\IsDefEq uses.)

The error is found only because the user stated the proposition to be proved. A kernel that only ran Infer\Infer would accept tt and silently assign it a different type. This is why goal comes first in every script, and why qed checks against it.

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

Using a hypothesis: modus ponens

The examples so far never apply a function. Consider checking

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}},

which says "if PP implies QQ and PP holds, then QQ holds". The four λ\lambdas extend the context to Γ=P:Sort 0,  Q:Sort 0,  f:P→Q,  p:P\Gamma = P : \Sort{0},\; Q : \Sort{0},\; f : P \to Q,\; p : P as before. The body f  pf\;p reaches the application case of Infer\Infer:

  • Infer(Γ,  f  p)\Infer(\Gamma,\; f\;p) T-App
    • Infer(Γ,  f)\Infer(\Gamma,\; f) returns P→QP \to Q, that is, Π(_:P). Q\tPi{\_}{P}{Q}. Whnf\Whnf leaves it unchanged, and it is a Π\Pi T-Var
    • Check(Γ,  p,  P)\Check(\Gamma,\; p,\; P) succeeds, since p:Pp : P is in Γ\Gamma T-Var, T-Conv
    • returns Q[_↦p]=Q\subst{Q}{\_}{p} = Q. The name _\_ does not occur in QQ, so the substitution does nothing

If the user had written f  ff\;f instead, the second step would call Check(Γ,f,P)\Check(\Gamma, f, P), which fails because P→QP \to Q is not PP: a function cannot be fed to itself here. Try it: change f p to f f below.

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

Instantiating a universal statement

In a dependent application the substitution in T-App does real work. Let id=λ(P:Sort 0). λ(h:P). h\mathit{id} = \tLam{P}{\Sort{0}}{\tLam{h}{P}{h}} be the proof from the first example, and let Γ=Q:Sort 0\Gamma = Q : \Sort{0}. Then Infer(Γ,id  Q)\Infer(\Gamma, \mathit{id}\;Q):

  • Infer(Γ,  id  Q)\Infer(\Gamma,\; \mathit{id}\;Q) T-App
    • Infer(Γ,  id)\Infer(\Gamma,\; \mathit{id}) returns Π(P:Sort 0). Π(h:P). P\tPi{P}{\Sort{0}}{\tPi{h}{P}{P}}, already a Π\Pi T-Lam
    • Check(Γ,  Q,  Sort 0)\Check(\Gamma,\; Q,\; \Sort{0}) succeeds T-Var
    • returns (Π(h:P). P)[P↦Q]=Π(h:Q). Q\subst{(\tPi{h}{P}{P})}{P}{Q} = \tPi{h}{Q}{Q}, that is, Q→QQ \to Q

Logically, this is ∀\forall-elimination: from "every proposition implies itself" we obtain "QQ implies QQ". The type of the result depends on the value of the argument QQ, which is what the dependent Π\Pi-type expresses and why T-App substitutes. In the explorer, id\mathit{id} is a defined constant and QQ comes from the context; the note on the T-App node records the substitution.

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

Conversion

Recall Id=λ(X:Sort 0). X\mathit{Id} = \tLam{X}{\Sort{0}}{X} from Chapter 3. The core system has no definitions, so Id  P\mathit{Id}\;P is shorthand for the term (λ(X:Sort 0). X)  P(\tLam{X}{\Sort{0}}{X})\;P. Consider checking

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 first checks that the annotation Id  P\mathit{Id}\;P is a type. InferSort(Γ1,Id  P)\InferSort(\Gamma_1, \mathit{Id}\;P) reaches the application case of Infer\Infer: the function λ(X:Sort 0). X\tLam{X}{\Sort{0}}{X} has type Π(X:Sort 0). Sort 0\tPi{X}{\Sort{0}}{\Sort{0}}, the argument PP passes Check(Γ1,P,Sort 0)\Check(\Gamma_1, P, \Sort{0}), and the result is Sort 0[X↦P]=Sort 0\subst{\Sort{0}}{X}{P} = \Sort{0}. So Id  P\mathit{Id}\;P is a type, and a proposition. The body hh then has type Id  P\mathit{Id}\;P, so

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}}.

Renaming bound variables alone does not turn this into Π(P:Sort 0). P→P\tPi{P}{\Sort{0}}{P \to P}. But IsDefEq\IsDefEq computes both sides before comparing them, and Id  P→βP\mathit{Id}\;P \betared P, so the inferred type becomes Π(P:Sort 0). Π(h:P). P\tPi{P}{\Sort{0}}{\tPi{h}{P}{P}} and the comparison succeeds as in the first example. In the typing rules, this step is a single use of T-Conv at the root of the derivation:

⋅⊢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}

The algorithm never decides where to apply T-Conv. As described in Chapter 5, conversion is hidden in the call to IsDefEq\IsDefEq, which runs only where two types are compared.

The live derivation shows where the computation happens: the annotation Id  P\mathit{Id}\;P is checked by T-App, and the inferred type still contains Id  P\mathit{Id}\;P until Check\Check compares it with the goal.

# The annotation of h is (fun (X : Sort 0) => X) P, which computes to P.
goal forall (P : Sort 0), P -> P
exact fun (P : Sort 0) => fun (h : (fun (X : Sort 0) => X) P) => h
qed

Exercises

Running Infer\Infer on these by hand, as in the examples above, is a good check of one's understanding of the algorithm. Each can also be tried in a playground.

Prove Π(P:Sort 0). Π(Q:Sort 0). P→Q→P\tPi{P}{\Sort{0}}{\tPi{Q}{\Sort{0}}{P \to Q \to P}}.

Solution

One answer is λ(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}}}}. The proof never uses qq: the system allows hypotheses to go unused.

Prove that implication is transitive: Π(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}}}.

Solution

One answer ends in λ(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)}}}; it uses the application case twice, first for f  p:Qf\;p : Q and then for 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

Explain why λ(P:Sort 0). λ(h:P). h  h\tLam{P}{\Sort{0}}{\tLam{h}{P}{h\;h}} is rejected, and at which line of Algorithm 5.1.

Solution

In the context P:Sort 0,h:PP : \Sort{0}, h : P, the body h  hh\;h reaches the application case. Infer\Infer returns PP for the function hh, and Whnf(P)=P\Whnf(P) = P is a variable, not a Π\Pi. The algorithm fails at the line else fail "not a function": a proof of PP is not something that can be applied. In Rust the message is application of non-function.

What to remember

  • A run of Infer\Infer mirrors a derivation tree; each returning call is one node.
  • Infer\Infer can succeed on a term that does not prove the stated theorem. Check\Check against the stated type is what catches it.
  • T-App is where hypotheses are used (modus ponens) and where universal statements are instantiated (substitution into the body of a Π\Pi).
  • Computation inside types is handled entirely by IsDefEq\IsDefEq; the algorithm never chooses where to apply T-Conv.