1. Untyped Lambda Calculus: De Bruijn Terms
import Mathlib.Logic.Relation
import Mathlib.Data.Set.Basic
import LeanLambda.Corenamespace Untyped1.1. Lambda Terms Without Types
The lambda calculus is a very small language for describing functions. It has no numbers, booleans, loops, or function declarations built in. There are only three ways to make a term:
-
a variable, such as
x; -
a function
λx. e, whose argument is calledxand whose body ise; -
an application
e₁ e₂, which applies the functione₁to the argumente₂.
The word untyped means that a lambda does not say what kind of argument it
expects. For now, λx. x, λx. x x, and even nonsensical-looking
applications are all legitimate terms. Types will be introduced only in the
next chapter.
Application associates to the left, so f x y means (f x) y.
The body of a lambda extends as far to the right as possible, so
λx. f x means λx. (f x).
A lambda binds occurrences of its variable in its body. In
λx. x y, the occurrence of x is bound, while y is free.
Names can be changed without changing the function: λx. x and
λy. y both return their argument. This harmless change of bound names
is called alpha-renaming.
Binder names are pleasant for humans but awkward for an implementation. Lean would normally regard the two identity functions above as different syntax trees, and substitution would constantly have to choose fresh names. We avoid that problem by implementing bound variables with de Bruijn indices first. Ordinary named notation will be added on top afterwards.
1.2. De Bruijn Terms
A de Bruijn index tells us how far away a variable's binder is. Index
0 refers to the nearest surrounding lambda, index 1 skips that
lambda and refers to the next one, and so on. Because the index records the
binder rather than its name, alpha-renaming disappears automatically.
For example:
-
λx. xbecomesλ 0; -
λx. λy. ybecomesλ λ 0; -
λx. λy. xbecomesλ λ 1.
The two occurrences of 0 in the first two examples refer to different
lambdas: in each case they select the nearest one. To find the binder of an
index, start at its occurrence and count lambdas while moving outwards.
We keep names for variables that have no binder inside the term. Thus the
implementation distinguishes a bound index 0 from a genuinely free
name such as x. This mixed representation lets us work with open terms
without introducing a separate representation for their surrounding context.
e ::= n \mid x \mid \lambda.\ e \mid e\ e
The datatype below follows this grammar exactly. The quotation DB[...]
is only convenient notation for constructing the corresponding syntax tree.
inductive DB where
| bound (index : Nat)
| free (x : String)
| lam (body : DB)
| app (fn arg : DB)
deriving DecidableEq, Repr, Lean.ToExprsyntax_rules dbTerm quoted_by "DB[" dbTerm "]" where
| "{" t:term "}" => t
| "(" t:dbTerm ")" => parse t
| n:num => DB.bound n
| x:ident => DB.free x
| "λ" body:dbTerm => DB.lam (parse body)
| f:dbTerm:70 a:dbTerm:71 => DB.app (parse f) (parse a)example : DB[x] = .free "x" := ⊢ DB.free "x" = DB.free "x" All goals completed! 🐙example : DB[λ λ 1] = .lam (.lam (.bound 1)) := ⊢ (DB.bound 1).lam.lam = (DB.bound 1).lam.lam All goals completed! 🐙example : DB[0 1 2] = .app (.app (.bound 0) (.bound 1)) (.bound 2) := ⊢ ((DB.bound 0).app (DB.bound 1)).app (DB.bound 2) = ((DB.bound 0).app (DB.bound 1)).app (DB.bound 2) All goals completed! 🐙namespace DB1.3. Free Variables and Scope
In a well-scoped de Bruijn term, every index refers to a surrounding lambda,
while every named variable is free. Thus FV collects precisely the
names stored by DB.free. Passing through a de Bruijn lambda does not
remove a name, because these lambdas bind indices rather than strings.
\begin{array}{rcl}
FV(n) &=& \varnothing\\
FV(x) &=& \{x\}\\
FV(\lambda.\ e) &=& FV(e)\\
FV(e_1\ e_2) &=& FV(e_1) \cup FV(e_2)
\end{array}
Our datatype also permits raw terms containing a dangling index. For
example, the term 0 has no free name, but it has no lambda for its index
to refer to either. We regard such a term as ill-scoped, not as a term with a
free named variable. The proposition ScopedAt d t says that every index
in t is smaller than the number d of binders supplied by its
surroundings. A term is locally closed when it needs no surrounding binders.
Consequently, a closed de Bruijn term must satisfy two independent conditions: it is locally closed, and it contains no free names.
@[simp] def FV : DB -> Finset String
| .bound _ => {}
| .free x => {x}
| .lam body => (FV body)
| .app f a => (FV f) ∪ (FV a)@[simp] def ScopedAt : Nat → DB → Prop
| depth, .bound n => n < depth
| _, .free _ => True
| depth, .lam body => ScopedAt (depth + 1) body
| depth, .app fn arg => ScopedAt depth fn ∧ ScopedAt depth argdef LocallyClosed (t : DB) : Prop := ScopedAt 0 tdef NoFreeVars (t : DB) : Prop :=
∀ x, x ∉ FV tdef Closed (t : DB) : Prop :=
LocallyClosed t ∧ NoFreeVars texample : "x" ∈ FV DB[x] := ⊢ "x" ∈ (free "x").FV All goals completed! 🐙example : NoFreeVars DB[0] := ⊢ (bound 0).NoFreeVars All goals completed! 🐙example : ¬ LocallyClosed DB[0] := ⊢ ¬(bound 0).LocallyClosed All goals completed! 🐙example : Closed DB[λ 0] := ⊢ (bound 0).lam.Closed All goals completed! 🐙1.4. Capture-Avoiding Substitution
Contracting a beta redex replaces the parameter of a lambda by its argument:
(\lambda x.\ e)\ s \longrightarrow e[x := s].
In ordinary named notation, e[x := s] means “replace the occurrences of
x that are free in the body e by s.” This replacement
must respect the lambdas already inside e. For example,
(\lambda x.\lambda y.\ x)\ y
must return a constant function whose result is the free variable y.
It must not become λy. y, which would turn that free variable into a
bound one. With de Bruijn indices the redex is (λ λ 1) y, and the
correct result is unambiguously λ y.
The implementation never substitutes for the string x. Instead, beta
reduction substitutes for index 0, which refers to the parameter of the
lambda being removed. We first define shifting, which adjusts an index when a
term passes beneath a binder, and then define substitution itself.
1.4.1. Shifting
Suppose a term contains an index referring to a lambda outside that term. If we place the term beneath one more lambda, its old binder is now one step farther away, so the index must increase. Indices already bound inside the term must remain unchanged.
The cutoff separates these cases. Indices smaller than c are protected
by lambdas already crossed during recursion. Indices at least c refer
farther outward and are incremented.
\begin{array}{rcl}
\uparrow_c(n) &=& \begin{cases}
n & n < c\\
n+1 & n \ge c
\end{cases}\\
\uparrow_c(x) &=& x\\
\uparrow_c(\lambda.\ e) &=& \lambda.\ \uparrow_{c+1}(e)\\
\uparrow_c(e_1\ e_2) &=& \uparrow_c(e_1)\ \uparrow_c(e_2)
\end{array}
Under a lambda the cutoff increases, because index 0 is bound by that
new lambda and must not move.
@[simp] def shiftRenaming (cutoff index : Nat) : Nat :=
if index < cutoff then index else index + 1@[simp] def shiftAbove (cutoff : Nat) : DB -> DB
| .bound k => .bound (shiftRenaming cutoff k)
| .free x => .free x
| .lam body => .lam (shiftAbove (cutoff + 1) body)
| .app f a => .app (shiftAbove cutoff f) (shiftAbove cutoff a)At cutoff zero, a bare index moves outward. The bound variable of an existing lambda is protected, while an index referring past that lambda still moves.
example : shiftAbove 0 DB[0] = DB[1] := ⊢ shiftAbove 0 (bound 0) = bound 1 All goals completed! 🐙example : shiftAbove 0 DB[λ 0] = DB[λ 0] := ⊢ shiftAbove 0 (bound 0).lam = (bound 0).lam All goals completed! 🐙example : shiftAbove 0 DB[λ 1] = DB[λ 2] := ⊢ shiftAbove 0 (bound 1).lam = (bound 2).lam All goals completed! 🐙1.4.2. Substitution
The expression subst j s t replaces index j in t by the
term s. Because the binder for index j is removed at the same
time, indices above j must decrease. The index case therefore has
exactly three possibilities:
[s/j]k =
\begin{cases}
k & k < j,\\
s & k = j,\\
k - 1 & j < k.
\end{cases}
An index below j refers to a nearer binder and is unchanged. Index
j is the variable being replaced. An index above j is decremented
because one binder has disappeared.
When substitution crosses a lambda, both sides of this description move one
level outward: the target becomes j + 1, and the substituted term is
shifted. This is the capture-avoiding step in the definition.
@[simp] def subst (j : Nat) (s : DB) : DB -> DB
| .bound k =>
if j > k then .bound k
else if j < k then .bound (k - 1)
else s
| .free x => .free x
| .lam body => .lam (subst (j + 1) (shiftAbove 0 s) body)
| .app f a => .app (subst j s f) (subst j s a)
The first example performs the replacement itself. In the second, removing
binder 0 brings the old index 1 one level nearer. In the third,
the inserted free name is unchanged when substitution crosses a lambda.
example : subst 0 DB[z] DB[0] = DB[z] := ⊢ subst 0 (free "z") (bound 0) = free "z" All goals completed! 🐙example : subst 0 DB[z] DB[1] = DB[0] := ⊢ subst 0 (free "z") (bound 1) = bound 0 All goals completed! 🐙example : subst 0 DB[z] DB[λ 1] = DB[λ z] := ⊢ subst 0 (free "z") (bound 1).lam = (free "z").lam All goals completed! 🐙For the outermost binder, the three variable cases simplify to the following calculation rules. These are the equations used most often in beta reduction.
\begin{array}{rcl}
[s/0]0 &=& s\\
[s/0](n+1) &=& n\\
[s/0]x &=& x
\end{array}
1.5. Beta Reduction
An application whose function is a lambda is called a beta redex:
(\lambda.\ e)\ s.
Contracting the redex removes the lambda and substitutes the argument for index
0 in the body. The resulting term is called the contractum:
(\lambda.\ e)\ s \to_\beta [s/0]e.
A larger term can contain several redexes. We therefore define beta reduction as a relation rather than as a function: one step chooses a single redex. The first rule below contracts a redex at the root; the remaining rules allow one step in the function, the argument, or the body of a lambda.
\frac{}{(\lambda.\ e)\ e' \to_\beta [e'/0]e}
\qquad
\frac{e_1 \to_\beta e_1'}{e_1\ e_2 \to_\beta e_1'\ e_2}
\qquad
\frac{e_2 \to_\beta e_2'}{e_1\ e_2 \to_\beta e_1\ e_2'}
\qquad
\frac{e \to_\beta e'}{\lambda.\ e \to_\beta \lambda.\ e'}
This is full beta reduction. In particular, reduction is allowed beneath a lambda. We have not chosen an evaluation strategy such as call-by-value or call-by-name.
Most calculations require more than one contraction. We write
e →βᵇ* e' for zero or more beta steps. “Zero or more” is important:
every term reduces to itself.
\frac{}{e \to_\beta^* e}
\qquad
\frac{e \to_\beta^* e' \qquad e' \to_\beta e''}{e \to_\beta^* e''}
Finally, a term is in normal form if it has no possible beta step. Because we use full beta reduction, a lambda is normal only when its body is normal.
inductive BetaStep : DB -> DB -> Prop where
| beta :
BetaStep (.app (.lam body) arg) (subst 0 arg body)
| app_left :
BetaStep f f' ->
BetaStep (.app f a) (.app f' a)
| app_right :
BetaStep a a' ->
BetaStep (.app f a) (.app f a')
| lam :
BetaStep body body' ->
BetaStep (.lam body) (.lam body')infix:50 " →βᵇ " => BetaStepabbrev BetaStar : DB -> DB -> Prop := Relation.ReflTransGen BetaStepinfix:50 " →βᵇ* " => BetaStardef Reducible (t : DB) : Prop := ∃ u, t →βᵇ u1.5.1. Calculating with Beta Reduction
For concrete calculations, beta_step constructs a single beta step and
beta_steps searches for a finite sequence of beta steps.
syntax "beta_step" : tacticmacro_rules | `(tactic| beta_step) => `(tactic| repeat' constructor)syntax "beta_steps" : tacticsyntax "aux_beta_steps" : tacticmacro_rules
| `(tactic| beta_steps) =>
`(tactic| first
| exact Relation.ReflTransGen.refl
| apply Relation.ReflTransGen.head
aux_beta_steps)
| `(tactic| aux_beta_steps) =>
`(tactic| first
| apply BetaStep.beta; beta_steps
| apply BetaStep.app_left; aux_beta_steps
| apply BetaStep.app_right; aux_beta_steps
| apply BetaStep.lam; aux_beta_steps)
The first calculation contracts an identity function. The second is the
capture-avoidance example from substitution: the free name y remains
free.
example : DB[(λ 0) x] →βᵇ DB[x] := ⊢ (bound 0).lam.app (free "x") →βᵇ free "x" All goals completed! 🐙example : DB[(λ λ 1) y] →βᵇ DB[λ y] := ⊢ (bound 1).lam.lam.app (free "y") →βᵇ (free "y").lam All goals completed! 🐙The next term contains two redexes, so either may be selected first. Both choices can be continued to the same result; confluence will later show that such agreement can always be found.
example : DB[((λ 0) x) ((λ 0) y)] →βᵇ DB[x ((λ 0) y)] := ⊢ ((bound 0).lam.app (free "x")).app ((bound 0).lam.app (free "y")) →βᵇ (free "x").app ((bound 0).lam.app (free "y")) All goals completed! 🐙example : DB[((λ 0) x) ((λ 0) y)] →βᵇ DB[((λ 0) x) y] := ⊢ ((bound 0).lam.app (free "x")).app ((bound 0).lam.app (free "y")) →βᵇ ((bound 0).lam.app (free "x")).app (free "y") All goals completed! 🐙example : DB[((λ 0) x) ((λ 0) y)] →βᵇ* DB[x y] := ⊢ ((bound 0).lam.app (free "x")).app ((bound 0).lam.app (free "y")) →βᵇ* (free "x").app (free "y") All goals completed! 🐙example : DB[((λ 0) x) ((λ 0) y)] →βᵇ* DB[x y] := ⊢ ((bound 0).lam.app (free "x")).app ((bound 0).lam.app (free "y")) →βᵇ* (free "x").app (free "y") calc
_ →βᵇ DB[x ((λ 0) y)] := ⊢ ((bound 0).lam.app (free "x")).app ((bound 0).lam.app (free "y")) →βᵇ (free "x").app ((bound 0).lam.app (free "y")) All goals completed! 🐙
_ →βᵇ DB[x y] := ⊢ (free "x").app ((bound 0).lam.app (free "y")) →βᵇ (free "x").app (free "y") All goals completed! 🐙Multi-step reduction is preserved by lambda and application. The final lemma combines reduction inside a redex with contraction of that redex.
@[grind .] theorem BetaStar.lam
(hsteps : body →βᵇ* body')
: .lam body →βᵇ* .lam body'
:= body:DBbody':DBhsteps:body →βᵇ* body'⊢ body.lam →βᵇ* body'.lam body:DBbody':DB⊢ body.lam →βᵇ* body.lambody:DBbody':DBb✝:DBc✝:DBa✝¹:Relation.ReflTransGen BetaStep body b✝a✝:b✝ →βᵇ c✝a_ih✝:body.lam →βᵇ* b✝.lam⊢ body.lam →βᵇ* c✝.lam body:DBbody':DB⊢ body.lam →βᵇ* body.lambody:DBbody':DBb✝:DBc✝:DBa✝¹:Relation.ReflTransGen BetaStep body b✝a✝:b✝ →βᵇ c✝a_ih✝:body.lam →βᵇ* b✝.lam⊢ body.lam →βᵇ* c✝.lam All goals completed! 🐙@[grind .] theorem BetaStar.app
: (fn →βᵇ* fn') → (arg →βᵇ* arg') → (.app fn arg →βᵇ* .app fn' arg')
:= fn:DBfn':DBarg:DBarg':DB⊢ fn →βᵇ* fn' → arg →βᵇ* arg' → fn.app arg →βᵇ* fn'.app arg' intro hfn fn:DBfn':DBarg:DBarg':DBhfn:fn →βᵇ* fn'harg:arg →βᵇ* arg'⊢ fn.app arg →βᵇ* fn'.app arg'; fn:DBfn':DBarg:DBarg':DBharg:arg →βᵇ* arg'⊢ fn.app arg →βᵇ* fn.app arg'fn:DBfn':DBarg:DBarg':DBharg:arg →βᵇ* arg'b✝:DBc✝:DBa✝¹:Relation.ReflTransGen BetaStep fn b✝a✝:b✝ →βᵇ c✝a_ih✝:fn.app arg →βᵇ* b✝.app arg'⊢ fn.app arg →βᵇ* c✝.app arg' fn:DBfn':DBarg:DBarg':DBharg:arg →βᵇ* arg'⊢ fn.app arg →βᵇ* fn.app arg'fn:DBfn':DBarg:DBarg':DBharg:arg →βᵇ* arg'b✝:DBc✝:DBa✝¹:Relation.ReflTransGen BetaStep fn b✝a✝:b✝ →βᵇ c✝a_ih✝:fn.app arg →βᵇ* b✝.app arg'⊢ fn.app arg →βᵇ* c✝.app arg' fn:DBfn':DBarg:DBarg':DBb✝:DBc✝:DBa✝¹:Relation.ReflTransGen BetaStep fn b✝a✝:b✝ →βᵇ c✝a_ih✝:fn.app arg →βᵇ* b✝.app arg⊢ fn.app arg →βᵇ* c✝.app argfn:DBfn':DBarg:DBarg':DBb✝¹:DBc✝¹:DBa✝³:Relation.ReflTransGen BetaStep fn b✝a✝²:b✝ →βᵇ c✝b✝:DBc✝:DBa✝¹:Relation.ReflTransGen BetaStep arg b✝a✝:b✝ →βᵇ c✝a_ih✝¹:fn.app arg →βᵇ* b✝¹.app b✝ → fn.app arg →βᵇ* c✝¹.app b✝a_ih✝:fn.app arg →βᵇ* b✝¹.app c✝⊢ fn.app arg →βᵇ* c✝¹.app c✝ fn:DBfn':DBarg:DBarg':DB⊢ fn.app arg →βᵇ* fn.app argfn:DBfn':DBarg:DBarg':DBb✝:DBc✝:DBa✝¹:Relation.ReflTransGen BetaStep arg b✝a✝:b✝ →βᵇ c✝a_ih✝:fn.app arg →βᵇ* fn.app b✝⊢ fn.app arg →βᵇ* fn.app c✝fn:DBfn':DBarg:DBarg':DBb✝:DBc✝:DBa✝¹:Relation.ReflTransGen BetaStep fn b✝a✝:b✝ →βᵇ c✝a_ih✝:fn.app arg →βᵇ* b✝.app arg⊢ fn.app arg →βᵇ* c✝.app argfn:DBfn':DBarg:DBarg':DBb✝¹:DBc✝¹:DBa✝³:Relation.ReflTransGen BetaStep fn b✝a✝²:b✝ →βᵇ c✝b✝:DBc✝:DBa✝¹:Relation.ReflTransGen BetaStep arg b✝a✝:b✝ →βᵇ c✝a_ih✝¹:fn.app arg →βᵇ* b✝¹.app b✝ → fn.app arg →βᵇ* c✝¹.app b✝a_ih✝:fn.app arg →βᵇ* b✝¹.app c✝⊢ fn.app arg →βᵇ* c✝¹.app c✝ All goals completed! 🐙@[grind .] theorem BetaStar.beta
: (body →βᵇ* body') → (arg →βᵇ* arg') → .app (.lam body) arg →βᵇ* subst 0 arg' body'
:= body:DBbody':DBarg:DBarg':DB⊢ body →βᵇ* body' → arg →βᵇ* arg' → body.lam.app arg →βᵇ* subst 0 arg' body' intro hbody body:DBbody':DBarg:DBarg':DBhbody:body →βᵇ* body'harg:arg →βᵇ* arg'⊢ body.lam.app arg →βᵇ* subst 0 arg' body'; calc
_ = .app (.lam body) arg := body:DBbody':DBarg:DBarg':DBhbody:body →βᵇ* body'harg:arg →βᵇ* arg'⊢ body.lam.app arg = body.lam.app arg All goals completed! 🐙
_ →βᵇ* .app (.lam body') arg' := body:DBbody':DBarg:DBarg':DBhbody:body →βᵇ* body'harg:arg →βᵇ* arg'⊢ body.lam.app arg →βᵇ* body'.lam.app arg' All goals completed! 🐙
_ →βᵇ subst 0 arg' body' := body:DBbody':DBarg:DBarg':DBhbody:body →βᵇ* body'harg:arg →βᵇ* arg'⊢ body'.lam.app arg' →βᵇ subst 0 arg' body' All goals completed! 🐙1.5.2. Normal and Neutral Terms
A term is in normal form when no beta step is possible. We express this positively by describing the possible shapes of such terms. A neutral term is a variable, possibly applied to normal arguments. A normal term is either a lambda with a normal body or a neutral term.
\frac{}{n\ \mathsf{neutral}}
\qquad
\frac{}{x\ \mathsf{neutral}}
\qquad
\frac{e_1\ \mathsf{neutral}\qquad e_2\ \mathsf{normal}}
{e_1\ e_2\ \mathsf{neutral}}
\frac{e\ \mathsf{normal}}{\lambda.\,e\ \mathsf{normal}}
\qquad
\frac{e\ \mathsf{neutral}}{e\ \mathsf{normal}}
mutual
inductive Normal : DB → Prop where
| lam : Normal body → Normal (.lam body)
| neutral : Neutral t → Normal t
inductive Neutral : DB → Prop where
| bound : Neutral (.bound n)
| free : Neutral (.free x)
| app : Neutral fn → Normal arg → Neutral (.app fn arg)
end
For concrete terms, normal repeatedly applies the rules defining normal
and neutral terms.
syntax "normal" : tacticmacro_rules | `(tactic| normal) => `(tactic| repeat' constructor)example : Normal DB[x] := ⊢ (free "x").Normal All goals completed! 🐙example : Normal DB[λ 0] := ⊢ (bound 0).lam.Normal All goals completed! 🐙example : Normal DB[x (λ 0)] := ⊢ ((free "x").app (bound 0).lam).Normal All goals completed! 🐙example : Neutral DB[x (λ 0)] := ⊢ ((free "x").app (bound 0).lam).Neutral All goals completed! 🐙1.5.3. Progress and Finality
1.5.3.1. Progress
Progress. Every term either takes a beta step or is normal. This is a property of lambda terms themselves; no typing assumption is needed.
theorem progress
: Reducible t ∨ Normal t
:= t:DB⊢ t.Reducible ∨ t.Normal
induction t with
x:String⊢ (free x).Reducible ∨ (free x).Normal
x:String⊢ (free x).Normal;
have : Neutral (.free x) := t:DB⊢ t.Reducible ∨ t.Normal All goals completed! 🐙
show : Normal (.free x) := x:Stringthis:(free x).Neutral⊢ (free x).Normal All goals completed! 🐙
n:ℕ⊢ (bound n).Reducible ∨ (bound n).Normal
n:ℕ⊢ (bound n).Normal;
have : Neutral (.bound n) := t:DB⊢ t.Reducible ∨ t.Normal All goals completed! 🐙
show : Normal (.bound n) := n:ℕthis:(bound n).Neutral⊢ (bound n).Normal All goals completed! 🐙
body:DBih:body.Reducible ∨ body.Normal⊢ body.lam.Reducible ∨ body.lam.Normal
obtain _ | _ : (Reducible body) ∨ (Normal body) := body:DBih:body.Reducible ∨ body.Normal⊢ body.Reducible ∨ body.Normal All goals completed! 🐙
body:DBih:body.Reducible ∨ body.Normalh✝:body.Reducible⊢ body.lam.Reducible ∨ body.lam.Normal body:DBih:body.Reducible ∨ body.Normalh✝:body.Reducible⊢ body.lam.Reducible;
obtain_by body' : body →βᵇ body' := body:DBih:body.Reducible ∨ body.Normalh✝:body.Reducible⊢ ∃ body', body →βᵇ body' All goals completed! 🐙
have : .lam body →βᵇ .lam body' := t:DB⊢ t.Reducible ∨ t.Normal All goals completed! 🐙
show : Reducible (.lam body) := body:DBih:body.Reducible ∨ body.Normalh✝¹:body.Reduciblebody':DBh✝:body →βᵇ body'this:body.lam →βᵇ body'.lam⊢ body.lam.Reducible body:DBih:body.Reducible ∨ body.Normalh✝¹:body.Reduciblebody':DBh✝:body →βᵇ body'this:body.lam →βᵇ body'.lam⊢ body.lam →βᵇ body'.lam; All goals completed! 🐙
body:DBih:body.Reducible ∨ body.Normalh✝:body.Normal⊢ body.lam.Reducible ∨ body.lam.Normal body:DBih:body.Reducible ∨ body.Normalh✝:body.Normal⊢ body.lam.Normal;
have : Normal body := t:DB⊢ t.Reducible ∨ t.Normal All goals completed! 🐙
show : Normal (.lam body) := body:DBih:body.Reducible ∨ body.Normalh✝:body.Normalthis:body.Normal⊢ body.lam.Normal All goals completed! 🐙
fn:DBarg:DBihfn:fn.Reducible ∨ fn.Normaliharg:arg.Reducible ∨ arg.Normal⊢ (fn.app arg).Reducible ∨ (fn.app arg).Normal
obtain _ | _ : (Reducible fn) ∨ (Normal fn) := fn:DBarg:DBihfn:fn.Reducible ∨ fn.Normaliharg:arg.Reducible ∨ arg.Normal⊢ fn.Reducible ∨ fn.Normal All goals completed! 🐙
fn:DBarg:DBihfn:fn.Reducible ∨ fn.Normaliharg:arg.Reducible ∨ arg.Normalh✝:fn.Reducible⊢ (fn.app arg).Reducible ∨ (fn.app arg).Normal fn:DBarg:DBihfn:fn.Reducible ∨ fn.Normaliharg:arg.Reducible ∨ arg.Normalh✝:fn.Reducible⊢ (fn.app arg).Reducible;
obtain_by fn' : fn →βᵇ fn' := fn:DBarg:DBihfn:fn.Reducible ∨ fn.Normaliharg:arg.Reducible ∨ arg.Normalh✝:fn.Reducible⊢ ∃ fn', fn →βᵇ fn' All goals completed! 🐙
have : .app fn arg →βᵇ .app fn' arg := t:DB⊢ t.Reducible ∨ t.Normal All goals completed! 🐙
show : Reducible (.app fn arg) := fn:DBarg:DBihfn:fn.Reducible ∨ fn.Normaliharg:arg.Reducible ∨ arg.Normalh✝¹:fn.Reduciblefn':DBh✝:fn →βᵇ fn'this:fn.app arg →βᵇ fn'.app arg⊢ (fn.app arg).Reducible fn:DBarg:DBihfn:fn.Reducible ∨ fn.Normaliharg:arg.Reducible ∨ arg.Normalh✝¹:fn.Reduciblefn':DBh✝:fn →βᵇ fn'this:fn.app arg →βᵇ fn'.app arg⊢ fn.app arg →βᵇ fn'.app arg; All goals completed! 🐙
obtain _ | _ : (Reducible arg) ∨ (Normal arg) := fn:DBarg:DBihfn:fn.Reducible ∨ fn.Normaliharg:arg.Reducible ∨ arg.Normalh✝:fn.Normal⊢ arg.Reducible ∨ arg.Normal All goals completed! 🐙
fn:DBarg:DBihfn:fn.Reducible ∨ fn.Normaliharg:arg.Reducible ∨ arg.Normalh✝¹:fn.Normalh✝:arg.Reducible⊢ (fn.app arg).Reducible ∨ (fn.app arg).Normal fn:DBarg:DBihfn:fn.Reducible ∨ fn.Normaliharg:arg.Reducible ∨ arg.Normalh✝¹:fn.Normalh✝:arg.Reducible⊢ (fn.app arg).Reducible;
obtain_by arg' : arg →βᵇ arg' := fn:DBarg:DBihfn:fn.Reducible ∨ fn.Normaliharg:arg.Reducible ∨ arg.Normalh✝¹:fn.Normalh✝:arg.Reducible⊢ ∃ arg', arg →βᵇ arg' All goals completed! 🐙
have : .app fn arg →βᵇ .app fn arg' := t:DB⊢ t.Reducible ∨ t.Normal All goals completed! 🐙
show : Reducible (.app fn arg) := fn:DBarg:DBihfn:fn.Reducible ∨ fn.Normaliharg:arg.Reducible ∨ arg.Normalh✝²:fn.Normalh✝¹:arg.Reduciblearg':DBh✝:arg →βᵇ arg'this:fn.app arg →βᵇ fn.app arg'⊢ (fn.app arg).Reducible fn:DBarg:DBihfn:fn.Reducible ∨ fn.Normaliharg:arg.Reducible ∨ arg.Normalh✝²:fn.Normalh✝¹:arg.Reduciblearg':DBh✝:arg →βᵇ arg'this:fn.app arg →βᵇ fn.app arg'⊢ fn.app arg →βᵇ fn.app arg'; All goals completed! 🐙
fn:DBarg:DBihfn:fn.Reducible ∨ fn.Normaliharg:arg.Reducible ∨ arg.Normalh✝¹:fn.Normalh✝:arg.Normal⊢ (fn.app arg).Reducible ∨ (fn.app arg).Normal cases ‹Normal fn› with
arg:DBiharg:arg.Reducible ∨ arg.Normalh✝:arg.Normalbody:DBa✝:body.Normalihfn:body.lam.Reducible ∨ body.lam.Normal⊢ (body.lam.app arg).Reducible ∨ (body.lam.app arg).Normal
arg:DBiharg:arg.Reducible ∨ arg.Normalh✝:arg.Normalbody:DBa✝:body.Normalihfn:body.lam.Reducible ∨ body.lam.Normal⊢ (body.lam.app arg).Reducible;
have : .app (.lam body) arg →βᵇ .subst 0 arg body := t:DB⊢ t.Reducible ∨ t.Normal All goals completed! 🐙
show : Reducible (.app (.lam body) arg) := arg:DBiharg:arg.Reducible ∨ arg.Normalh✝:arg.Normalbody:DBa✝:body.Normalihfn:body.lam.Reducible ∨ body.lam.Normalthis:body.lam.app arg →βᵇ subst 0 arg body⊢ (body.lam.app arg).Reducible arg:DBiharg:arg.Reducible ∨ arg.Normalh✝:arg.Normalbody:DBa✝:body.Normalihfn:body.lam.Reducible ∨ body.lam.Normalthis:body.lam.app arg →βᵇ subst 0 arg body⊢ body.lam.app arg →βᵇ subst 0 arg body; All goals completed! 🐙
fn:DBarg:DBihfn:fn.Reducible ∨ fn.Normaliharg:arg.Reducible ∨ arg.Normalh✝:arg.Normala✝:fn.Neutral⊢ (fn.app arg).Reducible ∨ (fn.app arg).Normal
fn:DBarg:DBihfn:fn.Reducible ∨ fn.Normaliharg:arg.Reducible ∨ arg.Normalh✝:arg.Normala✝:fn.Neutral⊢ (fn.app arg).Normal;
have : Neutral (.app fn arg) := t:DB⊢ t.Reducible ∨ t.Normal All goals completed! 🐙
show : Normal (.app fn arg) := fn:DBarg:DBihfn:fn.Reducible ∨ fn.Normaliharg:arg.Reducible ∨ arg.Normalh✝:arg.Normala✝:fn.Neutralthis:(fn.app arg).Neutral⊢ (fn.app arg).Normal All goals completed! 🐙1.5.3.2. Finality
Finality of normal forms. A normal term is not reducible. The proof follows the structure of the term: variables cannot step, a lambda can only step in its normal body, and an application headed by a neutral term is not a redex and cannot step inside either of its already-final components.
@[grind .] theorem Neutral.not_lam
: ¬ Neutral (.lam body)
:= body:DB⊢ ¬body.lam.Neutral body:DBhneutral:body.lam.Neutral⊢ False; All goals completed! 🐙@[grind .] theorem finality
(hnormal : Normal t)
: ¬ Reducible t
:= t:DBhnormal:t.Normal⊢ ¬t.Reducible
t:DBhnormal:t.Normala✝:t.Reducible⊢ False; obtain_by t' : t →βᵇ t' := t:DBhnormal:t.Normala✝:t.Reducible⊢ ∃ t', t →βᵇ t' All goals completed! 🐙
induction t generalizing t' with
n:ℕhnormal:(bound n).Normala✝:(bound n).Reduciblet':DBh✝:bound n →βᵇ t'⊢ False All goals completed! 🐙
x:Stringhnormal:(free x).Normala✝:(free x).Reduciblet':DBh✝:free x →βᵇ t'⊢ False All goals completed! 🐙
body:DBih:body.Normal → body.Reducible → ∀ (t' : DB), body →βᵇ t' → Falsehnormal:body.lam.Normala✝:body.lam.Reduciblet':DBh✝:body.lam →βᵇ t'⊢ False
cases ‹Normal (.lam body)› with
body:DBih:body.Normal → body.Reducible → ∀ (t' : DB), body →βᵇ t' → Falsea✝¹:body.lam.Reduciblet':DBh✝:body.lam →βᵇ t'a✝:body.lam.Neutral⊢ False All goals completed! 🐙
body:DBih:body.Normal → body.Reducible → ∀ (t' : DB), body →βᵇ t' → Falsea✝¹:body.lam.Reduciblet':DBh✝:body.lam →βᵇ t'a✝:body.Normal⊢ False
cases ‹.lam body →βᵇ t'› with
body:DBih:body.Normal → body.Reducible → ∀ (t' : DB), body →βᵇ t' → Falsea✝²:body.lam.Reduciblea✝¹:body.Normalbody':DBa✝:body →βᵇ body'⊢ False
have : Reducible body := t:DBhnormal:t.Normal⊢ ¬t.Reducible body:DBih:body.Normal → body.Reducible → ∀ (t' : DB), body →βᵇ t' → Falsea✝²:body.lam.Reduciblea✝¹:body.Normalbody':DBa✝:body →βᵇ body'⊢ body →βᵇ body'; All goals completed! 🐙
All goals completed! 🐙
fn:DBarg:DBfn_ih✝:fn.Normal → fn.Reducible → ∀ (t' : DB), fn →βᵇ t' → Falsearg_ih✝:arg.Normal → arg.Reducible → ∀ (t' : DB), arg →βᵇ t' → Falsehnormal:(fn.app arg).Normala✝:(fn.app arg).Reduciblet':DBh✝:fn.app arg →βᵇ t'⊢ False
have : Normal (.app fn arg) := t:DBhnormal:t.Normal⊢ ¬t.Reducible All goals completed! 🐙
have : Neutral (.app fn arg) := t:DBhnormal:t.Normal⊢ ¬t.Reducible fn:DBarg:DBfn_ih✝:fn.Normal → fn.Reducible → ∀ (t' : DB), fn →βᵇ t' → Falsearg_ih✝:arg.Normal → arg.Reducible → ∀ (t' : DB), arg →βᵇ t' → Falsehnormal:(fn.app arg).Normala✝¹:(fn.app arg).Reduciblet':DBh✝:fn.app arg →βᵇ t'a✝:(fn.app arg).Neutral⊢ (fn.app arg).Neutral; All goals completed! 🐙
have : Neutral fn ∧ Normal arg := t:DBhnormal:t.Normal⊢ ¬t.Reducible fn:DBarg:DBfn_ih✝:fn.Normal → fn.Reducible → ∀ (t' : DB), fn →βᵇ t' → Falsearg_ih✝:arg.Normal → arg.Reducible → ∀ (t' : DB), arg →βᵇ t' → Falsehnormal:(fn.app arg).Normala✝²:(fn.app arg).Reduciblet':DBh✝:fn.app arg →βᵇ t'this:(fn.app arg).Normala✝¹:fn.Neutrala✝:arg.Normal⊢ fn.Neutral ∧ arg.Normal; All goals completed! 🐙
cases ‹.app fn arg →βᵇ t'› with
arg:DBarg_ih✝:arg.Normal → arg.Reducible → ∀ (t' : DB), arg →βᵇ t' → Falsebody✝:DBfn_ih✝:body✝.lam.Normal → body✝.lam.Reducible → ∀ (t' : DB), body✝.lam →βᵇ t' → Falsehnormal:(body✝.lam.app arg).Normala✝:(body✝.lam.app arg).Reduciblethis✝¹:(body✝.lam.app arg).Normalthis✝:(body✝.lam.app arg).Neutralthis:body✝.lam.Neutral ∧ arg.Normal⊢ False All goals completed! 🐙
fn:DBarg:DBfn_ih✝:fn.Normal → fn.Reducible → ∀ (t' : DB), fn →βᵇ t' → Falsearg_ih✝:arg.Normal → arg.Reducible → ∀ (t' : DB), arg →βᵇ t' → Falsehnormal:(fn.app arg).Normala✝¹:(fn.app arg).Reduciblethis✝¹:(fn.app arg).Normalthis✝:(fn.app arg).Neutralthis:fn.Neutral ∧ arg.Normalfn':DBa✝:fn →βᵇ fn'⊢ False
have : Reducible fn := t:DBhnormal:t.Normal⊢ ¬t.Reducible fn:DBarg:DBfn_ih✝:fn.Normal → fn.Reducible → ∀ (t' : DB), fn →βᵇ t' → Falsearg_ih✝:arg.Normal → arg.Reducible → ∀ (t' : DB), arg →βᵇ t' → Falsehnormal:(fn.app arg).Normala✝¹:(fn.app arg).Reduciblethis✝¹:(fn.app arg).Normalthis✝:(fn.app arg).Neutralthis:fn.Neutral ∧ arg.Normalfn':DBa✝:fn →βᵇ fn'⊢ fn →βᵇ fn'; All goals completed! 🐙
All goals completed! 🐙
fn:DBarg:DBfn_ih✝:fn.Normal → fn.Reducible → ∀ (t' : DB), fn →βᵇ t' → Falsearg_ih✝:arg.Normal → arg.Reducible → ∀ (t' : DB), arg →βᵇ t' → Falsehnormal:(fn.app arg).Normala✝¹:(fn.app arg).Reduciblethis✝¹:(fn.app arg).Normalthis✝:(fn.app arg).Neutralthis:fn.Neutral ∧ arg.Normalarg':DBa✝:arg →βᵇ arg'⊢ False
have : Reducible arg := t:DBhnormal:t.Normal⊢ ¬t.Reducible fn:DBarg:DBfn_ih✝:fn.Normal → fn.Reducible → ∀ (t' : DB), fn →βᵇ t' → Falsearg_ih✝:arg.Normal → arg.Reducible → ∀ (t' : DB), arg →βᵇ t' → Falsehnormal:(fn.app arg).Normala✝¹:(fn.app arg).Reduciblethis✝¹:(fn.app arg).Normalthis✝:(fn.app arg).Neutralthis:fn.Neutral ∧ arg.Normalarg':DBa✝:arg →βᵇ arg'⊢ arg →βᵇ arg'; All goals completed! 🐙
All goals completed! 🐙1.5.3.3. Characterization of Normality
The judgment Normal was defined by the shape of a term, while
Reducible says that a beta step is possible. Progress and finality show
that these two possibilities are exact complements.
theorem normal_iff_not_reducible
: Normal t ↔ ¬ Reducible t
:= t:DB⊢ t.Normal ↔ ¬t.Reducible
t:DB⊢ t.Normal → ¬t.Reduciblet:DB⊢ ¬t.Reducible → t.Normal
t:DB⊢ t.Normal → ¬t.Reducible All goals completed! 🐙
t:DB⊢ ¬t.Reducible → t.Normal All goals completed! 🐙end DBend Untyped