Files
lean4/tests/elab_bench/cbv_system_f.lean
Garmelon 08eb78a5b2 chore: switch to new test/bench suite (#12590)
This PR sets up the new integrated test/bench suite. It then migrates
all benchmarks and some related tests to the new suite. There's also
some documentation and some linting.

For now, a lot of the old tests are left alone so this PR doesn't become
even larger than it already is. Eventually, all tests should be migrated
to the new suite though so there isn't a confusing mix of two systems.
2026-02-25 13:51:53 +00:00

1635 lines
63 KiB
Lean4
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Lean
/-!
# System F - Ported from Coq/Rocq
This file is a translation of the Rocq case study from:
*Definitional Proof Irrelevance Made Accessible*
Thiago Felicissimo, Yann Leray, Loïc Pujet, Nicolas Tabareau, Éric Tanter,
Théo Winterhalter
Used with permission of the authors.
It contains a formalization of System F, including substitution lemmas,
confluence of λ-calculus, and strong normalization proofs.
-/
/-- Binary trees with binders (using de Bruijn indices).
The parameter `A` is a phantom type allowing different notations
for λ-terms vs System F types. -/
inductive MakeTree (A : Type) : Type where
| Leaf : Nat MakeTree A
| Bind : MakeTree A MakeTree A
| Node : MakeTree A MakeTree A MakeTree A
deriving Repr, DecidableEq
namespace MakeTree
variable {A : Type}
/-!
## Shifting
`shift d t` increases every free variable of `t` which has an
index >= `d`. This is used in the definition of substitution.
-/
/-- Shift free variables with index >= d by 1 -/
def shift (depth : Nat) : MakeTree A MakeTree A
| Leaf n => if depth n then Leaf (n + 1) else Leaf n
| Bind t => Bind (shift (depth + 1) t)
| Node t1 t2 => Node (shift depth t1) (shift depth t2)
-- Notation: we'll use ↑ for shift
scoped notation "↑[" d "]" t => shift d t
@[simp, grind =]
theorem shift_leaf_ge {d n : Nat} (h : n d) : shift d (Leaf n : MakeTree A) = Leaf (n + 1) := by
grind [shift]
@[simp, grind =]
theorem shift_leaf_lt {d n : Nat} (h : n < d) : shift d (Leaf n : MakeTree A) = Leaf n := by
grind [shift]
@[simp, grind =]
theorem shift_bind (d : Nat) (t : MakeTree A) : shift d (Bind t) = Bind (shift (d + 1) t) := rfl
@[simp, grind =]
theorem shift_node (d : Nat) (t t' : MakeTree A) : shift d (Node t t') = Node (shift d t) (shift d t') := rfl
/-- Shifting commutes in a specific way
Coq: `shift_shift : forall (d d' : nat) (t : Tree), d' <= d -> ↑d'(↑d t) = ↑(S d)(↑d' t)` -/
@[grind _=_]
theorem shift_shift {d d' : Nat} (t : MakeTree A) (h : d' d) :
shift d' (shift d t) = shift (d + 1) (shift d' t) := by
induction t generalizing d d' <;> grind
/-!
## Substitution
`substitute n t t'` substitutes `t'` for the free variable of
index `n` in `t`, and decreases all free variables of `t`
which have an index > `n`.
-/
/-- Substitute t' for variable n in tree, decreasing variables > n -/
def substitute (n : Nat) (tree : MakeTree A) (tree' : MakeTree A) : MakeTree A :=
match tree with
| Leaf m =>
if m = n then tree'
else if m > n then Leaf (m - 1)
else Leaf m
| Bind t => Bind (substitute (n + 1) t (shift 0 tree'))
| Node t1 t2 => Node (substitute n t1 tree') (substitute n t2 tree')
-- Notation for substitution
scoped notation t "[" n "" u "]" => substitute n t u
@[simp, grind =]
theorem substitute_leaf_eq (n : Nat) (t : MakeTree A) : substitute n (Leaf n) t = t := by
grind [substitute]
@[simp, grind =]
theorem substitute_leaf_gt {n m : Nat} (t : MakeTree A) (h : m > n) :
substitute n (Leaf m) t = Leaf (m - 1) := by
grind [substitute]
@[simp, grind =]
theorem substitute_leaf_lt {n m : Nat} (t : MakeTree A) (h : m < n) :
substitute n (Leaf m) t = Leaf m := by
grind [substitute]
@[simp, grind =]
theorem substitute_bind (n : Nat) (t t' : MakeTree A) :
substitute n (Bind t) t' = Bind (substitute (n + 1) t (shift 0 t')) := rfl
@[simp, grind =]
theorem substitute_node (n : Nat) (t1 t2 t' : MakeTree A) :
substitute n (Node t1 t2) t' = Node (substitute n t1 t') (substitute n t2 t') := rfl
/-! ### Interaction between shift and substitute
Coq:
```
Lemma shift_substitute1 : forall (n d : nat) (t t' : Tree),
d <= n -> (↑d t)[S n↦↑d t'] = ↑d (t[n↦t']).
Lemma shift_substitute2 : forall (n d : nat) (t t' : Tree),
d >= n -> (↑(S d) t)[n↦↑d t'] = ↑d(t[n↦t']).
Lemma shift_substitute3 : forall (d : nat) (t t' : Tree),
(↑d t)[d↦t'] = t.
```
-/
/-- Shifting a term and then substituting at a shifted position cancels out -/
@[grind =]
theorem shift_substitute3 (d : Nat) (t t' : MakeTree A) :
substitute d (shift d t) t' = t := by
induction t generalizing d t' <;> grind
/-- First shift-substitute interaction lemma -/
@[grind =]
theorem shift_substitute1 {n d : Nat} (t t' : MakeTree A) (h : d n) :
substitute (n + 1) (shift d t) (shift d t') = shift d (substitute n t t') := by
induction t generalizing n d t' <;> grind [shift, substitute]
/-- Second shift-substitute interaction lemma -/
@[grind =]
theorem shift_substitute2 {n d : Nat} (t t' : MakeTree A) (h : d n) :
substitute n (shift (d + 1) t) (shift d t') = shift d (substitute n t t') := by
induction t generalizing n d t' <;> grind
/-! ### Substitution composition
Coq:
```
Lemma substitute_substitute : forall (n n' : nat) (t t' t'' : Tree),
n <= n' -> t[n↦t'][n'↦t''] = t[S n'↦↑n t''][n↦t'[n'↦t''] ].
```
-/
/-- Substitution composition lemma -/
theorem substitute_substitute {n n' : Nat} (t t' t'' : MakeTree A) (h : n n') :
substitute n' (substitute n t t') t'' =
substitute n (substitute (n' + 1) t (shift n t'')) (substitute n' t' t'') := by
induction t generalizing n n' t' t'' <;> grind [substitute]
/-! ### Free variables
Coq:
```
Fixpoint free_var (tree : Tree) :=
match tree with
| Leaf n => S n
| Bind t => pred (free_var t)
| Node t1 t2 => max (free_var t1) (free_var t2)
end.
```
`freeVar t` returns the least index n such that every free variable
appearing in `t` has index < n.
-/
/-- Returns the least upper bound on free variable indices (plus one) -/
def freeVar : MakeTree A Nat
| Leaf n => n + 1
| Bind t => (freeVar t) - 1
| Node t1 t2 => max (freeVar t1) (freeVar t2)
@[simp, grind =]
theorem freeVar_leaf (n : Nat) : freeVar (Leaf n : MakeTree A) = n + 1 := rfl
@[simp, grind =]
theorem freeVar_bind (t : MakeTree A) : freeVar (Bind t) = freeVar t - 1 := rfl
@[simp, grind =]
theorem freeVar_node (t1 t2 : MakeTree A) : freeVar (Node t1 t2) = max (freeVar t1) (freeVar t2) := rfl
/-- If d >= freeVar t, then shifting by d doesn't change t -/
@[grind =]
theorem shift_freeVar (t : MakeTree A) (d : Nat) (h : d freeVar t) :
shift d t = t := by
induction t generalizing d <;> grind [shift]
/-- If n >= freeVar t, then substituting for variable n doesn't change t -/
@[grind =]
theorem substitute_freeVar (t : MakeTree A) (n : Nat) (t' : MakeTree A) (h : n freeVar t) :
substitute n t t' = t := by
induction t generalizing n t' <;> grind
/-- Coq: Lemma id_substitute_shift_substitute : forall (t : Tree) (n p : nat),
n >= free_var t -> (↑p (t[p↦#n]))[S n↦#p] = t.
This states that substitution of a big enough variable can be reverted. -/
@[grind =]
theorem id_substitute_shift_substitute (t : MakeTree A) (n p : Nat) (h : n freeVar t) :
substitute (n + 1) (shift p (substitute p t (Leaf n))) (Leaf p) = t := by
induction t generalizing n p <;> grind
/-!
## Shift List (Parallel Shifting)
Coq:
```
Definition shift_list (depth : nat) (trees : list Tree) : list Tree :=
map (fun tree : Tree => ↑depth tree) trees.
```
-/
/-- Shift all trees in a list by depth d -/
def shiftList (depth : Nat) (trees : List (MakeTree A)) : List (MakeTree A) :=
trees.map (shift depth)
-- Notation: ⇑ for shiftList
scoped notation "⇑[" d "]" ts => shiftList d ts
-- Coq: Lemma shift_list_nil : forall (d : nat), ⇑d nil = nil.
@[simp, grind =]
theorem shiftList_nil (d : Nat) : shiftList d ([] : List (MakeTree A)) = [] := rfl
-- Coq: Lemma shift_list_cons : forall (d : nat) (t : Tree) (trees : list Tree),
-- ⇑d (t::trees) = ↑d t :: ⇑d trees.
@[simp, grind =]
theorem shiftList_cons (d : Nat) (t : MakeTree A) (trees : List (MakeTree A)) :
shiftList d (t :: trees) = shift d t :: shiftList d trees := rfl
-- Coq: Lemma shift_list_length : forall (d : nat) (trees : list Tree),
-- length (⇑d trees) = length trees.
@[simp, grind =]
theorem shiftList_length (d : Nat) (trees : List (MakeTree A)) :
(shiftList d trees).length = trees.length := by
grind [shiftList]
-- Coq: Lemma shift_list_nth : forall (d : nat) (trees : list Tree) (n : nat) (t : Tree),
-- nth n (⇑d trees) (↑d t) = ↑d (nth n trees t).
@[grind =]
theorem shiftList_getD (d : Nat) (trees : List (MakeTree A)) (n : Nat) (t : MakeTree A) :
(shiftList d trees).getD n (shift d t) = shift d (trees.getD n t) := by
simp only [shiftList, List.getD_eq_getElem?_getD, List.getElem?_map, Option.getD_map]
-- Coq: Lemma shift_list_shift_list : forall (d d' : nat) (trees : list Tree),
-- d' <= d -> ⇑d'(⇑d trees) = ⇑(S d)(⇑d' trees).
@[grind =]
theorem shiftList_shiftList {d d' : Nat} (trees : List (MakeTree A)) (h : d' d) :
shiftList d' (shiftList d trees) = shiftList (d + 1) (shiftList d' trees) := by
induction trees with
| nil => rfl
| cons t ts ih =>
simp only [shiftList_cons, shift_shift _ h, ih]
/-!
## Substitute List (Parallel Substitution)
Coq:
```
Fixpoint substitute_list (n : nat) (tree : Tree) (subst : list Tree) : Tree :=
match tree with
| Leaf m => if n <=? m then nth (m - n) subst (#(m - (length subst))) else #m
| Bind t => λ (substitute_list (S n) t (⇑0 subst))
| Node t1 t2 => (substitute_list n t1 subst)@(substitute_list n t2 subst)
end.
```
-/
/-- Parallel substitution: substitute the mth element of subst for free variable n+m,
and decrease all free variables >= n + length subst by length subst -/
def substituteList (n : Nat) (tree : MakeTree A) (subst : List (MakeTree A)) : MakeTree A :=
match tree with
| Leaf m =>
if n m then subst.getD (m - n) (Leaf (m - subst.length))
else Leaf m
| Bind t => Bind (substituteList (n + 1) t (shiftList 0 subst))
| Node t1 t2 => Node (substituteList n t1 subst) (substituteList n t2 subst)
-- Notation for substituteList
scoped notation t "[[" n "" s "]]" => substituteList n t s
-- Coq: Lemma substitute_list_leaf1 : forall (n m : nat) (s : list Tree),
-- m >= n -> #m[[n↦s]] = nth (m - n) s (#(m - length s)).
@[simp, grind =]
theorem substituteList_leaf_ge {n m : Nat} (s : List (MakeTree A)) (h : m n) :
substituteList n (Leaf m) s = s.getD (m - n) (Leaf (m - s.length)) := by
grind [substituteList]
-- Coq: Lemma substitute_list_leaf2 : forall (n m : nat) (s : list Tree),
-- m < n -> #m[[n↦s]] = #m.
@[simp, grind =]
theorem substituteList_leaf_lt {n m : Nat} (s : List (MakeTree A)) (h : m < n) :
substituteList n (Leaf m : MakeTree A) s = Leaf m := by
grind [substituteList]
-- Coq: Lemma substitute_list_bind
@[simp, grind =]
theorem substituteList_bind (n : Nat) (t : MakeTree A) (s : List (MakeTree A)) :
substituteList n (Bind t) s = Bind (substituteList (n + 1) t (shiftList 0 s)) := rfl
-- Coq: Lemma substitute_list_node
@[simp, grind =]
theorem substituteList_node (n : Nat) (t1 t2 : MakeTree A) (s : List (MakeTree A)) :
substituteList n (Node t1 t2) s = Node (substituteList n t1 s) (substituteList n t2 s) := rfl
-- Coq: Lemma substitute_list_nil : forall (n : nat) (t : Tree), t[[n↦nil]] = t.
@[grind =]
theorem substituteList_nil (n : Nat) (t : MakeTree A) : substituteList n t [] = t := by
induction t generalizing n <;> grind
-- Coq: Lemma substitute_list_cons : forall (n : nat) (t : Tree) (s : list Tree) (t' : Tree),
-- t[[n↦t'::s]] = t[[S n↦⇑n s]][n↦t'].
@[grind =]
theorem substituteList_cons (n : Nat) (t : MakeTree A) (s : List (MakeTree A)) (t' : MakeTree A) :
substituteList n t (t' :: s) = substitute n (substituteList (n + 1) t (shiftList n s)) t' := by
induction t generalizing n s t' <;> grind [shiftList]
-- Generalized version: substituteList at depth d with shifted identity list
-- This handles the case where we substitute at depth d with [Leaf d, Leaf (d+1), ..., Leaf (d+n-1)]
theorem substituteList_id_gen (t : MakeTree A) (d n : Nat) (h : freeVar t d + n) :
substituteList d t (List.map (fun i => Leaf (A := A) (d + i)) (List.range n)) = t := by
induction t generalizing d n with
| Leaf m =>
grind
| Bind t ih =>
congr 1
-- shiftList 0 (map (fun i => Leaf (d + i)) (range n)) = map (fun i => Leaf (d + 1 + i)) (range n)
have hshift : shiftList 0 (List.map (fun i => Leaf (A := A) (d + i)) (List.range n)) =
List.map (fun i => Leaf (A := A) ((d + 1) + i)) (List.range n) := by
simp only [shiftList, List.map_map]
congr 1
grind
grind [shiftList]
| Node t1 t2 ih1 ih2 =>
grind
-- Coq: Lemma id_subst : substituting identity (variables 0..n-1) gives back the same term
-- when free variables are bounded
theorem substituteList_id (t : MakeTree A) (n : Nat) (h : freeVar t n) :
substituteList 0 t (List.map (Leaf (A := A)) (List.range n)) = t := by
have hgen := substituteList_id_gen t 0 n (by grind)
simp only [Nat.zero_add] at hgen
exact hgen
end MakeTree
/-!
## Reflexive Transitive Closure and Confluence
Coq:
```
Definition confluent (R : A -> A -> Prop) : Prop :=
forall (t t' t'' : A),
R t t' -> R t t'' -> exists t''' : A, R t' t''' /\ R t'' t'''.
Inductive refl_trans_closure (R : A -> A -> Prop) : A -> A -> Prop :=
| Refl : forall (t : A), refl_trans_closure R t t
| Trans : forall (t t' t'' : A), (R t t') -> refl_trans_closure R t' t'' -> refl_trans_closure R t t''.
```
-/
/-- A relation R is confluent if whenever t reduces to both t' and t'',
there exists t''' such that both t' and t'' reduce to t''' -/
def Confluent {A : Type} (R : A A Prop) : Prop :=
t t' t'', R t t' R t t'' t''', R t' t''' R t'' t'''
/-- Reflexive transitive closure of a relation -/
@[grind]
inductive ReflTransClosure {A : Type} (R : A A Prop) : A A Prop where
| refl : t, ReflTransClosure R t t
| trans : t t' t'', R t t' ReflTransClosure R t' t'' ReflTransClosure R t t''
namespace ReflTransClosure
variable {A : Type} {R : A A Prop}
-- Coq: Lemma refl_trans_refl : forall (t : A), t ≻* t.
theorem refl_trans_refl (t : A) : ReflTransClosure R t t := refl t
-- Coq: Lemma refl_trans_trans : forall (t t' t'' : A), t ≻* t' -> t' ≻* t'' -> t ≻* t''.
theorem refl_trans_trans {t t' t'' : A} (h1 : ReflTransClosure R t t') (h2 : ReflTransClosure R t' t'') :
ReflTransClosure R t t'' := by
induction h1 with
| refl _ => exact h2
| trans _ _ _ hstep _ ih => exact trans _ _ _ hstep (ih h2)
-- Coq: Lemma refl_trans_confluent : confluent R -> confluent (refl_trans_closure R).
theorem refl_trans_confluent (hconf : Confluent R) : Confluent (ReflTransClosure R) := by
-- First prove auxiliary: if t ≻* t' and t ≻ t'', then ∃ t''', t' ≻ t''' ∧ t'' ≻* t'''
have aux : t t' t'', ReflTransClosure R t t' R t t'' t''', R t' t''' ReflTransClosure R t'' t''' := by
intro t t' t'' hred
induction hred generalizing t'' with
| refl _ =>
intro hstep
exact t'', hstep, refl _
| trans _ _ _ hstep0 _ ih =>
intro hstep''
obtain w, hredw1, hredw2 := hconf _ _ _ hstep0 hstep''
obtain u, hredu1, hredu2 := ih _ hredw1
exact u, hredu1, trans _ _ _ hredw2 hredu2
-- Now prove the main result
intro t t' t'' hred' hred''
induction hred'' generalizing t' with
| refl _ =>
exact t', refl _, hred'
| trans _ _ _ hstep0'' _ ih =>
obtain w, hredw1, hredw2 := aux _ _ _ hred' hstep0''
obtain u, hredu1, hredu2 := ih _ hredw2
exact u, refl_trans_trans (trans _ _ _ hredw1 (refl _)) hredu1, hredu2
-- Coq: Lemma refl_trans_incl : (forall t t', t ≻ t' -> t ≻'* t') -> (forall t t', t ≻* t' -> t ≻'* t').
theorem refl_trans_incl {R' : A A Prop}
(hinc : t t', R t t' ReflTransClosure R' t t')
{t t' : A} (hred : ReflTransClosure R t t') : ReflTransClosure R' t t' := by
induction hred with
| refl _ => grind
| trans _ _ _ hstep _ ih => apply refl_trans_trans (hinc _ _ hstep) <;> grind
end ReflTransClosure
/-!
## Lambda Terms and Parallel Reduction
We define a type alias for λ-terms and the parallel reduction relation (Krivine's ρ).
Coq:
```
Inductive TermParam :=.
Notation Term := (@MakeTree TermParam).
```
-/
/-- Phantom type for λ-terms -/
inductive TermParam : Type
/-- λ-terms are MakeTree with the TermParam phantom type -/
abbrev Term := MakeTree TermParam
namespace Term
open MakeTree
/-- Variable (de Bruijn index) -/
abbrev var (n : Nat) : Term := Leaf n
/-- Lambda abstraction -/
abbrev lam (t : Term) : Term := Bind t
/-- Application -/
abbrev app (t1 t2 : Term) : Term := Node t1 t2
/-!
### Parallel Reduction (red0)
Coq:
```
Inductive red0 : Term -> Term -> Prop :=
| Ctx_Var0 : forall (n : nat), red0 (#n) (#n)
| Beta0 : forall (t1 t1' t2 t2' : Term), red0 t1 t1' -> red0 t2 t2' -> red0 (λ t1@t2) (t1'[0↦t2'])
| Ctx_Abs0 : forall (t t' : Term), red0 t t' -> red0 (λ t) (λ t')
| Ctx_App0 : forall (t1 t1' t2 t2' : Term), red0 t1 t1' -> red0 t2 t2' -> red0 (t1@t2) (t1'@t2').
```
-/
/-- Parallel reduction relation (Krivine's ρ) -/
@[grind]
inductive red0 : Term Term Prop where
| Var : n, red0 (var n) (var n)
| Beta : t1 t1' t2 t2', red0 t1 t1' red0 t2 t2'
red0 (app (lam t1) t2) (substitute 0 t1' t2')
| Abs : t t', red0 t t' red0 (lam t) (lam t')
| App : t1 t1' t2 t2', red0 t1 t1' red0 t2 t2' red0 (app t1 t2) (app t1' t2')
-- Coq: Lemma red0_refl : forall (t : Term), t ≻₀ t.
@[refl, grind .]
theorem red0_refl : t : Term, red0 t t := by
intro t
induction t with grind
-- Coq: Lemma red0_shift : forall (t t' : Term) (d : nat), t ≻₀ t' -> ↑d t ≻₀ ↑d t'.
@[grind =>]
theorem red0_shift {t t' : Term} (d : Nat) (hred : red0 t t') : red0 (shift d t) (shift d t') := by
induction hred generalizing d with
| Var n => grind
| Beta t1 t1' t2 t2' _ _ ih1 ih2 =>
simp only [app, lam, shift_node, shift_bind]
rw [ shift_substitute2 t1' t2' (Nat.zero_le d)]
grind
| Abs t t' _ ih =>
grind
| App t1 t1' t2 t2' _ _ ih1 ih2 =>
grind
-- Coq: Lemma red0_subst : forall (t1 t1' t2 t2' : Term) (n : nat),
-- t1 ≻₀ t1' -> t2 ≻₀ t2' -> t1[n↦t2] ≻₀ t1'[n↦t2'].
@[grind =>]
theorem red0_subst {t1 t1' t2 t2' : Term} (n : Nat)
(hred1 : red0 t1 t1') (hred2 : red0 t2 t2') :
red0 (substitute n t1 t2) (substitute n t1' t2') := by
induction hred1 generalizing t2 t2' n with
| Var m => grind
| Beta t11 t11' t12 t12' _ _ ih1 ih2 =>
simp only [app, lam, substitute_node, substitute_bind]
rw [substitute_substitute _ _ _ (Nat.zero_le n)]
grind
| Abs t t' _ ih =>
simp only [lam, substitute_bind]
exact red0.Abs _ _ (ih (n + 1) (red0_shift 0 hred2))
| App t11 t11' t12 t12' _ _ ih1 ih2 =>
simp only [app, substitute_node]
exact red0.App _ _ _ _ (ih1 n hred2) (ih2 n hred2)
-- Coq: Lemma red0_confluent : confluent red0.
@[grind .]
theorem red0_confluent : Confluent red0 := by
intro t
induction t with
| Leaf n =>
intro t' t'' hred' hred''
cases hred' with
| Var => cases hred'' with
| Var => exact var n, red0.Var n, red0.Var n
| Bind t ih =>
intro t' t'' hred' hred''
cases hred' with
| Abs _ t0' hred0' => cases hred'' with
| Abs _ t0'' hred0'' =>
obtain t''', hred', hred'' := ih t0' t0'' hred0' hred0''
exact lam t''', red0.Abs _ _ hred', red0.Abs _ _ hred''
| Node t1 t2 ih1 ih2 =>
intro t' t'' hred' hred''
cases hred' with
| Beta t01 t01' t02 t02' hred01' hred02' =>
cases hred'' with
| Beta t001 t01'' t002 t02'' hred01'' hred02'' =>
-- Both are beta reductions
obtain t1''', hred1', hred1'' := ih1 (lam t01') (lam t01'') (red0.Abs _ _ hred01') (red0.Abs _ _ hred01'')
cases hred1' with
| Abs _ t001' hred001' =>
cases hred1'' with
| Abs _ t001'' hred001'' =>
obtain t2''', hred2', hred2'' := ih2 t02' t02'' hred02' hred02''
exact substitute 0 t001' t2''', red0_subst 0 hred001' hred2', red0_subst 0 hred001'' hred2''
| App t01'' t1'' t02'' t2'' hred1'' hred2'' =>
-- First is beta, second is app
cases hred1'' with
| Abs _ t001'' hred001'' =>
obtain t1''', hred01'', hred1''' := ih1 (lam t01') (lam t001'') (red0.Abs _ _ hred01') (red0.Abs _ _ hred001'')
cases hred01'' with
| Abs _ tBody hredBody =>
cases hred1''' with
| Abs _ tBody' hredBody' =>
obtain t2''', hred02'', hred2''' := ih2 t02' t2'' hred02' hred2''
exact substitute 0 tBody t2''', red0_subst 0 hredBody hred02'', red0.Beta _ _ _ _ hredBody' hred2'''
| App t01' t1' t02' t2' hred1' hred2' =>
cases hred'' with
| Beta t01 t1'' t02 t2'' hred1'' hred2'' =>
-- First is app, second is beta
cases hred1' with
| Abs _ t001' hred001' =>
obtain t2''', hred02', hred02'' := ih2 t2' t2'' hred2' hred2''
obtain t1''', hred01', hred01'' := ih1 (lam t001') (lam t1'') (red0.Abs _ _ hred001') (red0.Abs _ _ hred1'')
cases hred01' with
| Abs _ tBody hredBody =>
cases hred01'' with
| Abs _ tBody' hredBody' =>
exact substitute 0 tBody t2''', red0.Beta _ _ _ _ hredBody hred02', red0_subst 0 hredBody' hred02''
| App t01 t1'' t02 t2'' hred1'' hred2'' =>
-- Both are app
obtain t1''', hred1', hred1'' := ih1 t1' t1'' hred1' hred1''
obtain t2''', hred2', hred2'' := ih2 t2' t2'' hred2' hred2''
exact app t1''' t2''', red0.App _ _ _ _ hred1' hred2', red0.App _ _ _ _ hred1'' hred2''
/-!
### Single-step Beta Reduction
Coq:
```
Inductive beta : Term -> Term -> Prop :=
| Beta : forall (t t' : Term), beta ((λ t)@t') (t[0↦t'])
| Ctx_Abs : forall (t t' : Term), beta t t' -> beta (λ t) (λ t')
| Ctx_App_l : forall (t1 t1' : Term) (t2 : Term), beta t1 t1' -> beta (t1@t2) (t1'@t2)
| Ctx_App_r : forall (t1 t2 : Term) (t2' : Term), beta t2 t2' -> beta (t1@t2) (t1@t2').
```
-/
/-- Single-step beta reduction (Krivine's β₀) -/
@[grind]
inductive beta : Term Term Prop where
| Beta : t t', beta (app (lam t) t') (substitute 0 t t')
| Abs : t t', beta t t' beta (lam t) (lam t')
| AppL : t1 t1' t2, beta t1 t1' beta (app t1 t2) (app t1' t2)
| AppR : t1 t2 t2', beta t2 t2' beta (app t1 t2) (app t1 t2')
-- Coq: Lemma beta_red0_incl : forall (t t' : Term), t ≻ t' -> t ≻₀ t'.
@[grind .]
theorem beta_red0_incl {t t' : Term} (hred : beta t t') : red0 t t' := by
induction hred with
| Beta t t' => exact red0.Beta t t t' t' (red0_refl t) (red0_refl t')
| Abs _ _ _ ih => exact red0.Abs _ _ ih
| AppL _ _ t2 _ ih => exact red0.App _ _ _ _ ih (red0_refl t2)
| AppR t1 _ _ _ ih => exact red0.App _ _ _ _ (red0_refl t1) ih
-- Coq: Lemma beta_substitute : forall (t t' t'' : Term) (n : nat), t ≻ t' -> t[n↦t''] ≻ t'[n↦t''].
@[grind =>]
theorem beta_substitute {t t' : Term} (t'' : Term) (n : Nat) (hred : beta t t') :
beta (substitute n t t'') (substitute n t' t'') := by
induction hred generalizing t'' n with
| Beta t1 t2 =>
simp only [lam, substitute_node, substitute_bind]
rw [substitute_substitute _ _ _ (Nat.zero_le n)]
exact beta.Beta _ _
| Abs _ _ _ ih =>
simp only [lam, substitute_bind]
exact beta.Abs _ _ (ih (shift 0 t'') (n + 1))
| AppL _ _ _ _ ih =>
simp only [substitute_node]
exact beta.AppL _ _ _ (ih t'' n)
| AppR _ _ _ _ ih =>
simp only [substitute_node]
exact beta.AppR _ _ _ (ih t'' n)
/-!
### Strong Normalization
Coq:
```
Inductive Normalizing (t : Term) : Prop :=
| NormalizingInd : (forall (t' : Term), t ≻ t' -> Normalizing t') -> Normalizing t.
```
-/
/-- A term is normalizing if all its reducts are normalizing -/
inductive Normalizing : Term Prop where
| intro : ( t', beta t t' Normalizing t') Normalizing t
-- Coq: Lemma var_normalizing : forall (n : nat), Normalizing (#n).
@[grind .]
theorem var_normalizing (n : Nat) : Normalizing (var n) := by
apply Normalizing.intro
intro t' hred
cases hred
-- Coq: Lemma abs_normalizing : forall (t : Term), Normalizing t -> Normalizing (λ t).
@[grind .]
theorem abs_normalizing {t : Term} (hnorm : Normalizing t) : Normalizing (lam t) := by
induction hnorm with
| intro _ ih =>
apply Normalizing.intro
intro t' hred
cases hred with
| Abs _ t'' hred' => exact ih t'' hred'
-- Coq: Lemma normalizing_abs : forall (t : Term), Normalizing (λ t) -> Normalizing t.
@[grind ]
theorem normalizing_abs {t : Term} (hnorm : Normalizing (lam t)) : Normalizing t := by
-- Prove the more general statement: for any s, if Normalizing s and s = lam t, then Normalizing t
suffices h : s t, Normalizing s s = lam t Normalizing t from h (lam t) t hnorm rfl
intro s t hnorm heq
induction hnorm generalizing t with
| intro _ ih =>
subst heq
apply Normalizing.intro
intro t' hred
exact ih (lam t') (beta.Abs _ _ hred) t' rfl
@[grind ]
-- Coq: Lemma normalizing_app : forall (t1 t2 : Term), Normalizing (t1@t2) -> Normalizing t1 /\ Normalizing t2.
theorem normalizing_app {t1 t2 : Term} (hnorm : Normalizing (app t1 t2)) :
Normalizing t1 Normalizing t2 := by
-- Prove the more general statement
suffices h : s t1 t2, Normalizing s s = app t1 t2 Normalizing t1 Normalizing t2
from h (app t1 t2) t1 t2 hnorm rfl
intro s t1 t2 hnorm heq
induction hnorm generalizing t1 t2 with
| intro _ ih =>
subst heq
constructor
· apply Normalizing.intro
intro t1' hred
obtain hnorm1, _ := ih (app t1' t2) (by grind) t1' t2 rfl
grind
· apply Normalizing.intro
intro t2' hred
obtain _, hnorm2 := ih (app t1 t2') (by grind) t1 t2' rfl
grind
/-!
### Inversion lemmas for beta through shift/substitute
Coq:
```
Lemma shift_beta : forall (t t' : Term) (d : nat),
↑d t ≻ t' -> exists t'' : Term, t ≻ t'' /\ t' = ↑d t''.
```
-/
/-- If a shifted term reduces, the reduction came from the original term -/
theorem shift_beta {t t' : Term} (d : Nat) (hred : beta (shift d t) t') :
t'', beta t t'' t' = shift d t'' := by
induction t generalizing t' d with
| Leaf m =>
simp only [shift] at hred
split at hred <;> cases hred
| Bind tbody ih =>
simp only [shift_bind] at hred
cases hred with
| Abs _ t'' hred' =>
obtain t''', hred''', heq := ih (d + 1) hred'
exact lam t''', beta.Abs _ _ hred''', by simp [lam, shift_bind, heq]
| Node t1 t2 ih1 ih2 =>
-- First case on t1 to handle the Beta case
cases t1 with
| Leaf m =>
simp only [shift] at hred
split at hred
· -- d ≤ m, shift gives Leaf (m+1)
cases hred with
| AppL _ _ _ hred' => exact absurd hred' (by intro h; cases h)
| AppR _ _ _ hred' =>
obtain t2'', hred2'', heq2 := ih2 d hred'
exact app (Leaf m) t2'', beta.AppR _ _ _ hred2'', by simp [app, shift, *]
· -- m < d, shift gives Leaf m
cases hred with
| AppL _ _ _ hred' => exact absurd hred' (by intro h; cases h)
| AppR _ _ _ hred' =>
obtain t2'', hred2'', heq2 := ih2 d hred'
exact app (Leaf m) t2'', beta.AppR _ _ _ hred2'', by simp [app, shift, *]
| Bind t1body =>
simp only [shift_node, shift_bind] at hred
cases hred with
| Beta _ _ =>
refine substitute 0 t1body t2, beta.Beta _ _, ?_
exact shift_substitute2 t1body t2 (Nat.zero_le d)
| AppL _ _ _ hred' =>
-- hred' : beta (lam (shift (d+1) t1body)) t1'
-- ih1 is for Bind t1body, so we apply it directly to hred'
obtain t1'', hred1'', heq1 := ih1 d hred'
exact app t1'' t2, beta.AppL _ _ _ hred1'', by simp [app, shift_node, heq1]
| AppR _ _ _ hred' =>
obtain t2'', hred2'', heq2 := ih2 d hred'
exact app (lam t1body) t2'', beta.AppR _ _ _ hred2'',
by simp [app, lam, shift_node, shift_bind, heq2]
| Node t11 t12 =>
simp only [shift_node] at hred
cases hred with
| AppL _ _ _ hred' =>
obtain t1'', hred1'', heq1 := ih1 d hred'
exact app t1'' t2, beta.AppL _ _ _ hred1'', by simp [app, shift_node, heq1]
| AppR _ _ _ hred' =>
obtain t2'', hred2'', heq2 := ih2 d hred'
exact app (Node t11 t12) t2'', beta.AppR _ _ _ hred2'', by simp [app, shift_node, heq2]
-- Coq: Lemma shift_normalizing : forall (t : Term), Normalizing t -> Normalizing (↑0 t).
theorem shift_normalizing {t : Term} (hnorm : Normalizing t) : Normalizing (shift 0 t) := by
induction hnorm with
| intro _ ih =>
apply Normalizing.intro
intro t' hred
obtain t'', hred'', heq := shift_beta 0 hred
rw [heq]
exact ih t'' hred''
/-- Coq: Lemma substitute_leaf_beta : forall (t t' : Term) (p q : nat),
t[p↦#q] ≻ t' -> exists t'' : Term, t ≻ t'' /\ t' = t''[p↦#q]. -/
theorem substitute_leaf_beta {t t' : Term} (p q : Nat) (hred : beta (substitute p t (var q)) t') :
t'', beta t t'' t' = substitute p t'' (var q) := by
induction t generalizing t' p q with
| Leaf m =>
-- substitute p (Leaf m) (var q) is always a Leaf, so beta reduction is impossible
simp only [substitute, var] at hred
generalize hs : (if m = p then Leaf q else if m > p then Leaf (m - 1) else Leaf m) = s at hred
have hleaf : k, s = Leaf k := by
split at hs
· exact _, hs.symm
· split at hs <;> exact _, hs.symm
obtain k, hk := hleaf
rw [hk] at hred
cases hred
| Bind tbody ih =>
simp only [substitute_bind] at hred
cases hred with
| Abs _ t'' hred' =>
-- shift 0 (var q) = var (q+1) when 0 ≤ q, which is always true
simp only [var, shift_leaf_ge (Nat.zero_le q)] at hred'
obtain t''', hred''', heq := ih (p + 1) (q + 1) hred'
refine lam t''', beta.Abs _ _ hred''', ?_
simp only [lam, substitute_bind, var, shift_leaf_ge (Nat.zero_le q), heq]
| Node t1 t2 ih1 ih2 =>
simp only [substitute_node] at hred
-- Handle each case of t1 separately to avoid dependent elimination issues
cases t1 with
| Leaf m =>
-- substitute p (Leaf m) (var q) is always a Leaf, so Beta and non-trivial AppL are impossible
simp only [substitute] at hred
-- Use generalize to avoid dependent elimination issues
generalize hs1 : (if m = p then var q else if m > p then Leaf (m - 1) else Leaf m) = s1 at hred
-- First prove s1 is a Leaf
have hleaf : k, s1 = Leaf k := by
simp only [var] at hs1
split at hs1
· exact _, hs1.symm
· split at hs1 <;> exact _, hs1.symm
obtain k, hk := hleaf
rw [hk] at hred
-- Now hred : beta (app (Leaf k) s2) t', and Leaf can't be head of Beta or reduce via AppL
match hred with
| beta.AppL _ _ _ hred' => cases hred'
| beta.AppR _ _ _ hred' =>
obtain t2'', hred2'', heq2 := ih2 p q hred'
exact app (Leaf m) t2'', beta.AppR _ _ _ hred2'', by simp [app, substitute, *]
| Bind t1body =>
simp only [substitute_bind] at hred
match hred with
| beta.Beta _ _ =>
refine substitute 0 t1body t2, beta.Beta _ _, ?_
have h := substitute_substitute t1body t2 (var q) (Nat.zero_le p)
simp only [var, shift_leaf_ge (Nat.zero_le q)] at h
exact h.symm
| beta.AppL _ _ _ hred' =>
obtain t1'', hred1'', heq1 := ih1 p q hred'
exact app t1'' t2, beta.AppL _ _ _ hred1'', by simp [app, substitute_node, heq1]
| beta.AppR _ _ _ hred' =>
obtain t2'', hred2'', heq2 := ih2 p q hred'
exact app (lam t1body) t2'', beta.AppR _ _ _ hred2'',
by simp [app, lam, substitute_node, substitute_bind, heq2]
| Node t11 t12 =>
simp only [substitute_node] at hred
match hred with
| beta.AppL _ _ _ hred' =>
obtain t1'', hred1'', heq1 := ih1 p q hred'
exact app t1'' t2, beta.AppL _ _ _ hred1'', by simp [app, substitute_node, heq1]
| beta.AppR _ _ _ hred' =>
obtain t2'', hred2'', heq2 := ih2 p q hred'
exact app (Node t11 t12) t2'', beta.AppR _ _ _ hred2'', by simp [app, substitute_node, heq2]
-- Coq: Lemma substitute_leaf_normalizing : forall (t : Term) (p q : nat),
-- Normalizing t -> Normalizing (t[p↦#q]).
theorem substitute_leaf_normalizing {t : Term} (p q : Nat) (hnorm : Normalizing t) :
Normalizing (substitute p t (var q)) := by
induction hnorm with
| intro _ ih =>
apply Normalizing.intro
intro t' hred
obtain t'', hred'', heq := substitute_leaf_beta p q hred
rw [heq]
exact ih t'' hred''
/-- Coq: Lemma normalizing_substitute_leaf : forall (t : Term),
(forall (n : nat), Normalizing (t[0↦#n])) -> Normalizing t. -/
theorem normalizing_substitute_leaf {t : Term}
(h : n : Nat, Normalizing (substitute 0 t (var n))) : Normalizing t := by
rw [ id_substitute_shift_substitute t (freeVar t) 0 (by grind)]
apply substitute_leaf_normalizing
apply shift_normalizing
exact h (freeVar t)
grind_pattern normalizing_substitute_leaf => Normalizing (substitute 0 t (var _))
/-- Result of deciding whether a term is in normal form -/
inductive NormalDecResult (t : Term) : Type where
| isNormal : ( t', ¬ beta t t') NormalDecResult t
| hasReduct : (t' : Term) beta t t' NormalDecResult t
/-- Coq: Lemma normal_dec : forall (t : Term), (forall (t' : Term), ~ t ≻ t') + {t' : Term | t ≻ t'}.
Decides whether a term is in normal form, returning either a proof that no reduction exists,
or a witness reduction. -/
def normal_dec : (t : Term) NormalDecResult t
| Leaf n => NormalDecResult.isNormal fun t' hred => nomatch hred
| MakeTree.Bind t =>
match normal_dec t with
| NormalDecResult.isNormal hnorm =>
NormalDecResult.isNormal fun t' hred =>
match hred with
| beta.Abs _ t'' hred' => hnorm t'' hred'
| NormalDecResult.hasReduct t' hred =>
NormalDecResult.hasReduct (lam t') (beta.Abs _ _ hred)
| Node t1 t2 =>
match normal_dec t1 with
| NormalDecResult.hasReduct t1' hred1 =>
NormalDecResult.hasReduct (app t1' t2) (beta.AppL _ _ _ hred1)
| NormalDecResult.isNormal hnorm1 =>
match normal_dec t2 with
| NormalDecResult.hasReduct t2' hred2 =>
NormalDecResult.hasReduct (app t1 t2') (beta.AppR _ _ _ hred2)
| NormalDecResult.isNormal hnorm2 =>
match t1 with
| Leaf n =>
NormalDecResult.isNormal fun t' hred =>
match hred with
| beta.AppL _ _ _ hred' => nomatch hred'
| beta.AppR _ _ _ hred' => hnorm2 _ hred'
| MakeTree.Bind t1body =>
NormalDecResult.hasReduct (substitute 0 t1body t2) (beta.Beta _ _)
| Node t11 t12 =>
NormalDecResult.isNormal fun t' hred =>
match hred with
| beta.AppL _ _ _ hred' => hnorm1 _ hred'
| beta.AppR _ _ _ hred' => hnorm2 _ hred'
/-- Result of computing a normal form -/
structure NormalFormResult (t : Term) : Type where
normalForm : Term
reduction : ReflTransClosure beta t normalForm
isNormal : t'', ¬ beta normalForm t''
/-- Reducts of normalizing terms are normalizing -/
theorem normalizing_step : t, Normalizing t t', beta t t' Normalizing t' := by
intro t hnorm t' hred
cases hnorm with
| intro h => exact h t' hred
/-- Well-founded relation on normalizing terms -/
instance : WellFoundedRelation { t : Term // Normalizing t } where
rel := fun t', _ t, _ => beta t t'
wf := fun t, hn =>
hn.recOn (motive := fun t _ => Acc (fun (p' p : { t : Term // Normalizing t }) => beta p.1 p'.1) t, _)
(fun h ih => Acc.intro _ (fun t', _ hred => ih t' hred))
/-- Coq: Lemma normal_form : forall (t : Term), Normalizing t -> {t' : Term | t ≻* t' /\ forall t'', ~ t' ≻ t''}.
Computes the normal form of a normalizing term. -/
def normal_form (t : Term) (hnorm : Normalizing t) : NormalFormResult t :=
match normal_dec t with
| NormalDecResult.isNormal hnorm' =>
t, ReflTransClosure.refl t, hnorm'
| NormalDecResult.hasReduct t' hred =>
let hnorm' := normalizing_step t hnorm t' hred
let t'', red'', norm'' := normal_form t' hnorm'
t'', ReflTransClosure.trans t t' t'' hred red'', norm''
termination_by (t, hnorm : { t : Term // Normalizing t })
/-!
## Reducibility Candidates
Definition of closure under β-reduction, neutrality, saturation,
and reducibility candidates for proving strong normalization.
-/
/-- Coq: Definition RedClosed (X : Term -> Prop) : Prop := forall (t t' : Term), X t -> t ≻ t' -> X t'.
A set of terms is closed under reduction if it is preserved by beta reduction. -/
def RedClosed (X : Term Prop) : Prop :=
t t', X t beta t t' X t'
/-- Coq: Inductive Neutral : Term -> Prop := | Var_Neutral | App_Neutral.
A term is neutral if it is not an abstraction. -/
inductive Neutral : Term Prop where
| Var : n, Neutral (var n)
| App : t1 t2, Neutral (app t1 t2)
/-- Coq: Definition Saturated (X : Term -> Prop) := forall (t : Term), Neutral t -> (forall (t' : Term), t ≻ t' -> X t') -> X t.
A set of terms is saturated if a neutral term is in the set whenever all its reducts are. -/
def Saturated (X : Term Prop) : Prop :=
t, Neutral t ( t', beta t t' X t') X t
/-- Coq: Definition RedCand (X : Term -> Prop) := (normalizing) /\ RedClosed /\ Saturated.
A reducibility candidate is a reduction-closed saturated set of normalizing terms. -/
def RedCand (X : Term Prop) : Prop :=
( t, X t Normalizing t) RedClosed X Saturated X
-- Coq: Lemma RedCand_Normalizing
@[grind =>]
theorem RedCand_Normalizing {X : Term Prop} (hrc : RedCand X) : t, X t Normalizing t :=
hrc.1
-- Coq: Lemma RedCand_RedClosed
@[grind =>]
theorem RedCand_RedClosed {X : Term Prop} (hrc : RedCand X) : RedClosed X :=
hrc.2.1
-- Coq: Lemma RedCand_Saturated
@[grind =>]
theorem RedCand_Saturated {X : Term Prop} (hrc : RedCand X) : Saturated X :=
hrc.2.2
/-- Coq: Lemma RedCandVar : forall (X : Term -> Prop) (n : nat), RedCand X -> X (#n).
A reducibility candidate contains all variables. -/
@[grind =>]
theorem RedCandVar {X : Term Prop} (n : Nat) (hrc : RedCand X) : X (var n) := by
apply RedCand_Saturated hrc
· exact Neutral.Var n
· intro t' hred
cases hred
/-- Coq: Lemma SNRedCand : RedCand Normalizing.
The set of all normalizing terms is a reducibility candidate. -/
theorem SNRedCand : RedCand Normalizing := by
refine fun _ h => h, ?_, ?_
· -- RedClosed
intro t t' hnorm hred
cases hnorm with
| intro h => exact h t' hred
· -- Saturated
intro t _ ih
exact Normalizing.intro ih
/-- Coq: Definition RedCandArrow (X Y : Term -> Prop) (t1 : Term) : Prop := forall (t2 : Term), X t2 -> Y (t1@t2).
The arrow of two sets of terms. -/
def RedCandArrow (X Y : Term Prop) (t1 : Term) : Prop :=
t2, X t2 Y (app t1 t2)
/-- Coq: Lemma red_cand_arrow_RedCand : The arrow of two reducibility candidates is a reducibility candidate. -/
@[grind .]
theorem red_cand_arrow_RedCand {X Y : Term Prop} (hrcX : RedCand X) (hrcY : RedCand Y) :
RedCand (RedCandArrow X Y) := by
refine ?_, ?_, ?_
· -- Normalizing
intro t1 ht1
have h := ht1 (var 0) (RedCandVar 0 hrcX)
exact (normalizing_app (RedCand_Normalizing hrcY _ h)).1
· -- RedClosed
intro t1 t1' ht1 hred t2 ht2
apply RedCand_RedClosed hrcY
· exact ht1 t2 ht2
· exact beta.AppL _ _ _ hred
· -- Saturated
intro t1 hneutral ht1 t2 ht2
have hnorm := RedCand_Normalizing hrcX _ ht2
induction hnorm with
| intro _ ih =>
apply RedCand_Saturated hrcY
· exact Neutral.App t1 _
· intro t' hred
match hred with
| beta.Beta _ _ =>
cases hneutral
| beta.AppL _ t1' _ hred1 =>
exact ht1 t1' hred1 _ ht2
| beta.AppR _ _ t2' hred2 =>
apply ih t2' hred2
exact RedCand_RedClosed hrcX _ _ ht2 hred2
/-- Coq: Lemma red_cand_arrow_substitute : If for any t2∈X, t1[0↦t2]∈Y, then λt1 ∈ RedCandArrow X Y. -/
theorem red_cand_arrow_substitute {X Y : Term Prop} {t1 : Term}
(hrcX : RedCand X) (hrcY : RedCand Y)
(hsub : t2, X t2 Y (substitute 0 t1 t2)) :
RedCandArrow X Y (lam t1) := by
-- Use normalizing_substitute_leaf to get normalization of t1
have ht1_norm : Normalizing t1 := by grind
-- Now induct on normalization of t1
induction ht1_norm with
| intro h1 ih1 =>
intro t2 ht2
have hnorm2 := RedCand_Normalizing hrcX _ ht2
induction hnorm2 with
| intro h2 ih2 =>
apply RedCand_Saturated hrcY
· exact Neutral.App (lam _) _
· intro t' hred
match hred with
| beta.Beta _ _ => exact hsub _ ht2
| beta.AppL _ _ _ hred1 =>
match hred1 with
| beta.Abs _ t1' hred1' =>
apply ih1 t1' hred1'
· intro t2' ht2'
exact RedCand_RedClosed hrcY _ _ (hsub t2' ht2') (beta_substitute t2' 0 hred1')
· grind
| beta.AppR _ _ t2' hred2 =>
apply ih2 t2' hred2
exact RedCand_RedClosed hrcX _ _ ht2 hred2
end Term
/-!
## System F Types
System F types are also represented using MakeTree, with a different phantom type.
- Leaf n = type variable &n
- Bind ty = universal type ∀ty
- Node ty1 ty2 = arrow type ty1 ⇒ ty2
-/
/-- Coq: Inductive TypeFParam :=. Phantom type for System F types. -/
inductive TypeFParam
deriving Repr
/-- System F types -/
abbrev TypeF := MakeTree TypeFParam
namespace TypeF
/-- Type variable -/
abbrev tvar (n : Nat) : TypeF := MakeTree.Leaf n
/-- Universal type -/
abbrev forallTy (ty : TypeF) : TypeF := MakeTree.Bind ty
/-- Arrow type -/
abbrev arrow (ty1 ty2 : TypeF) : TypeF := MakeTree.Node ty1 ty2
-- Convenient notations matching Coq
scoped notation:max "&." n:arg => tvar n
scoped notation:max "∀." ty:10 => forallTy ty
scoped infixr:30 "" => arrow
end TypeF
/-!
## Type Contexts
A type context assigns a set of terms (reducibility candidate) to each type variable.
-/
/-- Coq: shift_t_ctx d t_ctx X shifts the type context, inserting X at position d. -/
def shift_t_ctx (d : Nat) (t_ctx : Nat Term Prop) (X : Term Prop) : Nat Term Prop :=
fun n =>
match compare n d with
| .eq => X
| .lt => t_ctx n
| .gt => t_ctx (n - 1)
-- Coq: Lemma shift_t_ctx1
@[grind =]
theorem shift_t_ctx_eq (d : Nat) (t_ctx : Nat Term Prop) (X : Term Prop) :
shift_t_ctx d t_ctx X d = X := by
grind [shift_t_ctx]
-- Coq: Lemma shift_t_ctx2
@[grind =]
theorem shift_t_ctx_gt {d n : Nat} (t_ctx : Nat Term Prop) (X : Term Prop)
(h : n d) : shift_t_ctx d t_ctx X (n + 1) = t_ctx n := by
grind [shift_t_ctx]
-- Coq: Lemma shift_t_ctx3
@[grind =]
theorem shift_t_ctx_lt {d n : Nat} (t_ctx : Nat Term Prop) (X : Term Prop)
(h : n < d) : shift_t_ctx d t_ctx X n = t_ctx n := by
grind [shift_t_ctx]
/-!
## Reducibility Candidates for Types
For each System F type and type context, we define the corresponding reducibility candidate.
-/
open Term in
/-- Coq: red_cand t_ctx ty t - the term t is in the reducibility candidate for type ty. -/
def red_cand (t_ctx : Nat Term Prop) : TypeF Term Prop
| MakeTree.Leaf n => t_ctx n
| MakeTree.Bind ty => fun t => X, RedCand X red_cand (shift_t_ctx 0 t_ctx X) ty t
| MakeTree.Node ty1 ty2 => RedCandArrow (red_cand t_ctx ty1) (red_cand t_ctx ty2)
-- Helper for shift_t_ctx at 0
private theorem shift_t_ctx_0_RedCand {t_ctx : Nat Term Prop} {X : Term Prop}
(hrcX : Term.RedCand X) (hctx : n, Term.RedCand (t_ctx n)) :
n, Term.RedCand (shift_t_ctx 0 t_ctx X n) := by
intro n
simp only [shift_t_ctx]
cases n with
| zero => simp; exact hrcX
| succ m => simp [Nat.compare_eq_gt.mpr (Nat.zero_lt_succ m)]; exact hctx m
-- shift_t_ctx composition lemma
@[grind =]
private theorem shift_t_ctx_shift_t_ctx (d : Nat) (t_ctx : Nat Term Prop) (X Y : Term Prop) :
shift_t_ctx (d + 1) (shift_t_ctx 0 t_ctx Y) X = shift_t_ctx 0 (shift_t_ctx d t_ctx X) Y := by
funext n t
simp only [shift_t_ctx]
rcases Nat.lt_trichotomy n (d + 1) with hlt | heq | hgt
· simp only [Nat.compare_eq_lt.mpr hlt]
rcases Nat.lt_trichotomy n 0 with hlt0 | heq0 | hgt0
· exact absurd hlt0 (Nat.not_lt_zero n)
· simp only [heq0, Nat.compare_eq_eq.mpr rfl]
· simp only [Nat.compare_eq_gt.mpr hgt0, Nat.compare_eq_lt.mpr (by omega : n - 1 < d)]
· simp only [heq, Nat.compare_eq_eq.mpr rfl, Nat.compare_eq_gt.mpr (Nat.zero_lt_succ d), Nat.succ_sub_one]
· simp only [Nat.compare_eq_gt.mpr hgt, Nat.compare_eq_gt.mpr (by omega : n > 0),
Nat.compare_eq_gt.mpr (by omega : n - 1 > d), Nat.compare_eq_gt.mpr (by omega : n - 1 > 0),
Nat.sub_sub]
-- Coq: Lemma red_cand_RedCand - red_cand always produces a reducibility candidate
open Term in
@[grind .]
theorem red_cand_RedCand (t_ctx : Nat Term Prop) (ty : TypeF)
(hctx : n, RedCand (t_ctx n)) : RedCand (red_cand t_ctx ty) := by
induction ty generalizing t_ctx with
| Leaf n => exact hctx n
| Bind ty ih =>
refine ?_, ?_, ?_
· -- Normalizing
intro t ht
have h := ht Normalizing SNRedCand
exact RedCand_Normalizing (ih _ (shift_t_ctx_0_RedCand SNRedCand hctx)) _ h
· -- RedClosed
intro t t' ht hred X hrcX
exact RedCand_RedClosed (ih _ (shift_t_ctx_0_RedCand hrcX hctx)) _ _ (ht X hrcX) hred
· -- Saturated
intro t hneutral ht X hrcX
apply RedCand_Saturated (ih _ (shift_t_ctx_0_RedCand hrcX hctx))
· exact hneutral
· intro t' hred
exact ht t' hred X hrcX
| Node ty1 ty2 ih1 ih2 =>
exact red_cand_arrow_RedCand (ih1 t_ctx hctx) (ih2 t_ctx hctx)
/-!
## System F Typing
Typing derivations for System F terms.
-/
/-- Coq: Inductive CompatSubst : compatible substitution between type context and term context.
A substitution s is compatible with a typing context ctx in type context t_ctx if
each term s[i] belongs to the reducibility candidate for ctx[i]. -/
@[grind]
inductive CompatSubst (t_ctx : Nat Term Prop) : List TypeF List Term Prop where
| Nil : CompatSubst t_ctx [] []
| Cons : ctx s ty t, CompatSubst t_ctx ctx s
red_cand t_ctx ty t CompatSubst t_ctx (ty :: ctx) (t :: s)
open Term in
/-- Coq: Inductive TermF : list TypeF -> Term -> TypeF -> Type.
System F typing derivation. ctx ⊢ t : ty means t has type ty in context ctx. -/
inductive TermF : List TypeF Term TypeF Type where
| VarF0 : TermF (ty :: ctx) (var 0) ty
| VarFS : TermF ctx (var n) ty' TermF (ty :: ctx) (var (n + 1)) ty'
| AbsF : TermF (ty :: ctx) t ty' TermF ctx (lam t) (TypeF.arrow ty ty')
| AppF : TermF ctx t (TypeF.arrow ty ty') TermF ctx t' ty TermF ctx (app t t') ty'
| TAbsF : TermF (ctx.map (MakeTree.shift 0)) t ty TermF ctx t (TypeF.forallTy ty)
| TAppF : TermF ctx t (TypeF.forallTy ty) TermF ctx t (MakeTree.substitute 0 ty ty')
-- Coq: Lemma red_cand_shift - shifting preserves reducibility candidates
open Term in
theorem red_cand_shift (t_ctx : Nat Term Prop) (X : Term Prop) (ty : TypeF) (d : Nat)
(_hrcX : RedCand X) (hctx : n, RedCand (t_ctx n)) (t : Term) :
red_cand t_ctx ty t red_cand (shift_t_ctx d t_ctx X) (MakeTree.shift d ty) t := by
induction ty generalizing t_ctx d t with
| Leaf n =>
unfold red_cand MakeTree.shift
split
· -- d ≤ n: shift gives Leaf (n+1), shift_t_ctx (n+1) at d = t_ctx n
rename_i h
simp only
rw [shift_t_ctx_gt t_ctx X h]
· -- d > n: shift gives Leaf n, shift_t_ctx n at d = t_ctx n
rename_i h
simp only
rw [shift_t_ctx_lt t_ctx X (Nat.not_le.mp h)]
| Bind ty ih =>
show ( Y, RedCand Y red_cand (shift_t_ctx 0 t_ctx Y) ty t)
( Y, RedCand Y red_cand (shift_t_ctx 0 (shift_t_ctx d t_ctx X) Y) (MakeTree.shift (d+1) ty) t)
constructor
· intro ht Y hrcY
have hih := ih (shift_t_ctx 0 t_ctx Y) (d + 1) (shift_t_ctx_0_RedCand hrcY hctx) t
grind
· intro ht Y hrcY
have hih := ih (shift_t_ctx 0 t_ctx Y) (d + 1) (shift_t_ctx_0_RedCand hrcY hctx) t
grind
| Node ty1 ty2 ih1 ih2 =>
show ( t2, red_cand t_ctx ty1 t2 red_cand t_ctx ty2 (app t t2))
( t2, red_cand (shift_t_ctx d t_ctx X) (MakeTree.shift d ty1) t2
red_cand (shift_t_ctx d t_ctx X) (MakeTree.shift d ty2) (app t t2))
constructor <;> grind
-- Coq: Lemma red_cand_shift_substitute - substitution in types corresponds to shift_t_ctx
-- Uses well-founded recursion on ty.size since Bind case needs different ty'
open Term in
theorem red_cand_shift_substitute (t_ctx : Nat Term Prop) (ty ty' : TypeF) (n : Nat)
(hctx : m, RedCand (t_ctx m)) (t : Term) :
red_cand (shift_t_ctx n t_ctx (red_cand t_ctx ty')) ty t
red_cand t_ctx (MakeTree.substitute n ty ty') t := by
match ty with
| .Leaf m =>
simp only [red_cand]
rcases Nat.lt_trichotomy m n with hlt | heq | hgt
· -- m < n
simp [MakeTree.substitute_leaf_lt ty' hlt, red_cand, shift_t_ctx_lt _ _ hlt]
· -- m = n
simp [heq, MakeTree.substitute_leaf_eq n ty', shift_t_ctx_eq]
· -- m > n
simp [MakeTree.substitute_leaf_gt ty' hgt, red_cand, shift_t_ctx, Nat.compare_eq_gt.mpr hgt]
| .Bind ty_inner =>
simp only [MakeTree.substitute_bind, red_cand]
constructor
· intro ht Y hrcY
have hctx' : m, RedCand (shift_t_ctx 0 t_ctx Y m) := shift_t_ctx_0_RedCand hrcY hctx
have hrc_eq : t', red_cand t_ctx ty' t' red_cand (shift_t_ctx 0 t_ctx Y) (MakeTree.shift 0 ty') t' :=
red_cand_shift t_ctx Y ty' 0 hrcY hctx
have h_eq_pred : red_cand t_ctx ty' = red_cand (shift_t_ctx 0 t_ctx Y) (MakeTree.shift 0 ty') := by
funext t'; exact propext (hrc_eq t')
have hshift_ctx := shift_t_ctx_shift_t_ctx n t_ctx (red_cand t_ctx ty') Y
have h1 : red_cand (shift_t_ctx (n + 1) (shift_t_ctx 0 t_ctx Y) (red_cand t_ctx ty')) ty_inner t := by
rw [hshift_ctx]; exact ht Y hrcY
-- Recursive call with smaller ty_inner and shifted ty'
have ih := red_cand_shift_substitute (shift_t_ctx 0 t_ctx Y) ty_inner (MakeTree.shift 0 ty') (n + 1) hctx' t
grind
· intro ht Y hrcY
have hctx' : m, RedCand (shift_t_ctx 0 t_ctx Y m) := shift_t_ctx_0_RedCand hrcY hctx
have hrc_eq : t', red_cand t_ctx ty' t' red_cand (shift_t_ctx 0 t_ctx Y) (MakeTree.shift 0 ty') t' :=
red_cand_shift t_ctx Y ty' 0 hrcY hctx
have h_eq_pred : red_cand t_ctx ty' = red_cand (shift_t_ctx 0 t_ctx Y) (MakeTree.shift 0 ty') := by grind
have hshift_ctx := shift_t_ctx_shift_t_ctx n t_ctx (red_cand t_ctx ty') Y
have ih := red_cand_shift_substitute (shift_t_ctx 0 t_ctx Y) ty_inner (MakeTree.shift 0 ty') (n + 1) hctx' t
grind
| .Node ty1 ty2 =>
simp only [MakeTree.substitute_node, red_cand, RedCandArrow]
have ih1 := red_cand_shift_substitute t_ctx ty1 ty' n hctx
have ih2 := red_cand_shift_substitute t_ctx ty2 ty' n hctx
grind
termination_by ty
-- Helper: shifting preserves compatibility
open Term in
theorem compat_shift (t_ctx : Nat Term Prop) (ctx : List TypeF) (s : List Term)
(X : Term Prop) (hrcX : RedCand X) (hctx : n, RedCand (t_ctx n))
(hcompat : CompatSubst t_ctx ctx s) :
CompatSubst (shift_t_ctx 0 t_ctx X) (ctx.map (MakeTree.shift 0)) s := by
induction hcompat with try grind
| Cons ctx s ty t _ hrc ih =>
apply CompatSubst.Cons
· grind
· rw [ red_cand_shift t_ctx X ty 0 hrcX hctx]
grind
-- Coq: Lemma compat_id - identity substitution is compatible
-- Helper: identity substitution starting from index k
open Term in
theorem compat_id_aux (t_ctx : Nat Term Prop) (ctx : List TypeF) (k : Nat)
(hctx : n, RedCand (t_ctx n)) :
CompatSubst t_ctx ctx (List.map var (List.range' k ctx.length)) := by
induction ctx generalizing k with try grind
| cons ty ctx ih =>
apply CompatSubst.Cons
· grind
· apply RedCandVar
grind
open Term in
theorem compat_id (t_ctx : Nat Term Prop) (ctx : List TypeF)
(hctx : n, RedCand (t_ctx n)) : CompatSubst t_ctx ctx (List.map var (List.range ctx.length)) := by
grind [compat_id_aux, List.range_eq_range']
-- Coq: Lemma context_length - variable index < context length
theorem context_length_aux {ctx : List TypeF} {t : Term} {ty : TypeF}
(hderiv : TermF ctx t ty) : n, t = Term.var n ctx.length > n := by
induction hderiv <;> grind
open Term in
theorem context_length {ctx : List TypeF} {n : Nat} {ty : TypeF}
(hderiv : TermF ctx (var n) ty) : ctx.length > n :=
context_length_aux hderiv n rfl
-- Coq: Lemma free_var_context_length - free variables bounded by context length
open Term MakeTree in
theorem free_var_context_length {ctx : List TypeF} {t : Term} {ty : TypeF}
(hderiv : TermF ctx t ty) : freeVar t ctx.length := by
induction hderiv <;> grind
-- Coq: Lemma termf_red_cand - fundamental theorem of logical relations
-- This is the key lemma: well-typed terms belong to their reducibility candidate
open Term MakeTree in
theorem termf_red_cand (t_ctx : Nat Term Prop) (ctx : List TypeF) (s : List Term)
(t : Term) (ty : TypeF)
(hctx : n, RedCand (t_ctx n))
(hcompat : CompatSubst t_ctx ctx s)
(hderiv : TermF ctx t ty) :
red_cand t_ctx ty (substituteList 0 t s) := by
induction hderiv generalizing t_ctx s with try grind
| AbsF hderiv' ih =>
simp only [lam, substituteList_bind]
apply red_cand_arrow_substitute
· grind
· grind
· intro t2 ht2
have ih' := ih t_ctx (t2 :: s) hctx (CompatSubst.Cons _ s _ t2 hcompat ht2)
grind
| @AppF ctx' t1 ty' ty'' t2 _ _ ih1 ih2 =>
simp only [app, substituteList_node]
exact ih1 t_ctx s hctx hcompat _ (ih2 t_ctx s hctx hcompat)
| TAbsF _ ih =>
intro X hrcX
apply ih (shift_t_ctx 0 t_ctx X) s
· intro n
simp only [shift_t_ctx]
match n with
| 0 => grind
| m + 1 => simp only [Nat.compare_eq_gt.mpr (Nat.zero_lt_succ m)]; grind
· exact compat_shift t_ctx _ s X hrcX hctx hcompat
| TAppF _ ih =>
have h := ih t_ctx s hctx hcompat
apply (red_cand_shift_substitute t_ctx _ _ 0 hctx _).mp
apply h (red_cand t_ctx _) (red_cand_RedCand t_ctx _ hctx)
-- Coq: Lemma termf_norm : any well-typed term is normalizing
open Term MakeTree in
theorem termf_norm {ctx : List TypeF} {t : Term} {ty : TypeF}
(hderiv : TermF ctx t ty) : Normalizing t := by
-- Use substituteList with identity substitution (variables)
have hfv := free_var_context_length hderiv
have hid : substituteList 0 t (List.map var (List.range ctx.length)) = t :=
substituteList_id t ctx.length hfv
rw [ hid]
have hrc := red_cand_RedCand (fun _ => Normalizing) ty (fun _ => SNRedCand)
apply RedCand_Normalizing hrc
exact termf_red_cand (fun _ => Normalizing) ctx _ t ty
(fun _ => SNRedCand)
(compat_id (fun _ => Normalizing) ctx (fun _ => SNRedCand))
hderiv
-- Coq: Lemma compat_length - compatible substitution preserves length
theorem compat_length {t_ctx : Nat Term Prop} {ctx : List TypeF} {s : List Term}
(hcompat : CompatSubst t_ctx ctx s) : ctx.length = s.length := by
induction hcompat with
| Nil => rfl
| Cons _ _ _ _ _ _ ih => simp [ih]
-- Coq: Lemma omegaTypeF - example typing derivation for the omega combinator
-- nil ⊢ λ(#0@#0) : (∀α. α⇒α) ⇒ (∀α. α⇒α)
open Term in
example : TermF [] (lam (app (var 0) (var 0)))
(TypeF.arrow (TypeF.forallTy (TypeF.arrow (TypeF.tvar 0) (TypeF.tvar 0)))
(TypeF.forallTy (TypeF.arrow (TypeF.tvar 0) (TypeF.tvar 0)))) :=
.AbsF <| .AppF (.TAppF .VarF0) .VarF0
-- Coq: Lemma termf_normal_form - computes the normal form of any well-typed term
open Term MakeTree in
def termf_normal_form {ctx : List TypeF} {t : Term} {ty : TypeF}
(hderiv : TermF ctx t ty) : NormalFormResult t :=
normal_form t (termf_norm hderiv)
--------- Performance testing materials not ported from Rocq
-- Church numeral type: ∀α. (αα) → αα
open TypeF in
def fNatType : TypeF := . &.0 ( &.0 &.0) &.0
/--
info: MakeTree.Bind
(MakeTree.Node
(MakeTree.Leaf 0)
(MakeTree.Node (MakeTree.Node (MakeTree.Leaf 0) (MakeTree.Leaf 0)) (MakeTree.Leaf 0)))
-/
#guard_msgs in
#eval fNatType
-- Church zero: λx. λf. x (type: ∀α. α → (αα) → α)
def fNatZero : Term := .lam (.lam (.var 1))
def fNatZeroTyped : TermF ctx fNatZero fNatType :=
.TAbsF <| .AbsF <| .AbsF <| .VarFS <| .VarF0
-- Church successor: λn. λx. λf. f (n x f) (swapped x and f)
def fNatSucc : Term := .lam (.lam (.lam (.app (.var 0) (.app (.app (.var 2) (.var 1)) (.var 0)))))
open TypeF in
def fNatSuccTyped : TermF ctx fNatSucc (fNatType fNatType) :=
-- λn : fNatType
.AbsF <|
-- ∀α introduction (shifts context)
.TAbsF <|
-- λx : α
.AbsF <|
-- λf : αα
.AbsF <|
-- f (n x f) : apply f to (n x f)
.AppF .VarF0 -- f : αα (index 0)
-- n x f : instantiate n at α, apply to x, apply to f
(.AppF
(.AppF
(.TAppF <| .VarFS <| .VarFS <| .VarF0) -- n at index 2, instantiated at α
(.VarFS <| .VarF0)) -- x at index 1
.VarF0) -- f at index 0
-- Church addition: λm. λn. λx. λf. m (n x f) f (swapped x and f)
def fNatAdd : Term := .lam (.lam (.lam (.lam
(.app (.app (.var 3) (.app (.app (.var 2) (.var 1)) (.var 0))) (.var 0)))))
open TypeF in
def fNatAddTyped : TermF ctx fNatAdd (fNatType fNatType fNatType) :=
-- λm : fNatType
.AbsF <|
-- λn : fNatType
.AbsF <|
-- ∀α introduction
.TAbsF <|
-- λx : α
.AbsF <|
-- λf : αα
.AbsF <|
-- m (n x f) f
.AppF
(.AppF
(.TAppF <| .VarFS <| .VarFS <| .VarFS <| .VarF0) -- m at index 3, instantiated at α
(.AppF
(.AppF
(.TAppF <| .VarFS <| .VarFS <| .VarF0) -- n at index 2, instantiated at α
(.VarFS <| .VarF0)) -- x at index 1
.VarF0)) -- f at index 0
.VarF0 -- f at index 0
open TypeF in
def fNatAddTyped' (x : TermF ctx t fNatType) (y : TermF ctx t' fNatType) : TermF ctx (.app (.app fNatAdd t) t') fNatType :=
.AppF (.AppF fNatAddTyped x) y
-- Build f^n(x) = f (f (... (f x)...)) with n applications
-- In context where f is var 0 and x is var 1
def fNatBody : Nat Term
| 0 => .var 1 -- x
| n + 1 => .app (.var 0) (fNatBody n) -- f (f^n(x))
-- Church numeral for n in normal form: λx. λf. f^n(x)
def fNatOfNat (n : Nat) : Term := .lam (.lam (fNatBody n))
-- Helper: typing derivation for f^n(x) : α
-- Context is [αα, α, ...] where f : αα at index 0, x : α at index 1
open TypeF in
def fNatBodyTyped (n : Nat) :
TermF ((&.0 &.0) :: &.0 :: ctx) (fNatBody n) &.0 :=
match n with
| 0 => .VarFS <| .VarF0 -- x : α at index 1
| n + 1 => .AppF .VarF0 (fNatBodyTyped n) -- f (f^n(x))
-- Typing derivation: fNatOfNat n : fNatType
def fNatOfNatTyped (n : Nat) : TermF ctx (fNatOfNat n) fNatType :=
.TAbsF <| .AbsF <| .AbsF <| fNatBodyTyped n
example : fNatOfNat 0 = fNatZero := rfl
example : fNatOfNat 3 = .lam (.lam (.app (.var 0) (.app (.var 0) (.app (.var 0) (.var 1))))) := rfl
-- Church multiplication: λm. λn. λx. λf. n x (λy. m y f)
-- Computes m*n by applying (λy. m y f) = "apply f m times" n times, starting from x
def fNatMult : Term := .lam (.lam (.lam (.lam (
.app (.app (.var 2) (.var 1))
(.lam (.app (.app (.var 4) (.var 0)) (.var 1)))))))
open TypeF in
def fNatMultTyped : TermF ctx fNatMult (fNatType fNatType fNatType) :=
.AbsF <| .AbsF <| .TAbsF <| .AbsF <| .AbsF <|
.AppF
(.AppF (.TAppF <| .VarFS <| .VarFS <| .VarF0) (.VarFS <| .VarF0))
(.AbsF <| .AppF (.AppF (.TAppF <| .VarFS <| .VarFS <| .VarFS <| .VarFS <| .VarF0) .VarF0) (.VarFS <| .VarF0))
-- Church exponentiation: exp b e = e 1 (mult b)
-- where 1 = λx.λf. f x, and (mult b) n = b * n
-- This iterates "multiply by b" e times, starting from 1
-- Note: uses higher-order instantiation (e at type fNatType → fNatType)
def fNatExp : Term :=
-- λb. λe. e 1 (λn. mult b n)
-- where 1 and mult are inlined
.lam <| .lam <| -- λb. λe.
.app (.app (.var 0) -- e applied to:
(.lam (.lam (.app (.var 0) (.var 1))))) -- Church 1 = λx.λf. f x
(.lam <| -- λn.
.lam <| .lam <| -- λx. λf. (result of mult b n)
.app (.app (.var 2) (.var 1)) -- n x applied to:
(.lam (.app (.app (.var 5) (.var 0)) (.var 1)))) -- λy. b y f
-- The typing requires instantiating e at type fNatType (higher-order instantiation)
-- e : ∀α. α → (αα) → α is instantiated at fNatType to get
-- e[fNatType] : fNatType → (fNatType → fNatType) → fNatType
-- Then applied to Church 1 : fNatType and (λn. mult b n) : fNatType → fNatType
open TypeF in
def fNatExpTyped : TermF ctx fNatExp (fNatType fNatType fNatType) :=
-- λb : fNatType
.AbsF <|
-- λe : fNatType
.AbsF <|
-- e[fNatType] 1 (mult b)
.AppF
(.AppF
-- e instantiated at fNatType (e is at index 0)
(.TAppF <| .VarF0)
-- Church 1 = λx.λf. f x : fNatType
(.TAbsF <| .AbsF <| .AbsF <|
.AppF .VarF0 (.VarFS <| .VarF0))) -- f x
-- λn. mult b n : fNatType → fNatType
(.AbsF <| -- λn : fNatType (now n=0, e=1, b=2)
.TAbsF <| -- introduces α
.AbsF <| -- λx : α (now x=0, n=1, e=2, b=3)
.AbsF <| -- λf : αα (now f=0, x=1, n=2, e=3, b=4)
-- body: (n x) (λy. (b y) f)
.AppF
(.AppF
(.TAppF <| .VarFS <| .VarFS <| .VarF0) -- n at index 2
(.VarFS <| .VarF0)) -- x at index 1
(.AbsF <| -- λy : α (now y=0, f=1, x=2, n=3, e=4, b=5)
.AppF
(.AppF
(.TAppF <| .VarFS <| .VarFS <| .VarFS <| .VarFS <| .VarFS <| .VarF0) -- b at index 5
.VarF0) -- y at index 0
(.VarFS <| .VarF0))) -- f at index 1
def testTerm (n : Nat) : Term :=
.app (.app fNatExp (fNatOfNat 2)) (fNatOfNat n)
-- testTerm n computes 2^n and is always well-typed
def testTermTyped (n : Nat) : TermF ctx (testTerm n) fNatType :=
.AppF (.AppF fNatExpTyped (fNatOfNatTyped 2)) (fNatOfNatTyped n)
-- 2^n + 2^n
def doublePow2 (n : Nat) : Term :=
.app (.app fNatAdd (testTerm n)) (testTerm n)
def doublePow2Typed (n : Nat) : TermF ctx (doublePow2 n) fNatType :=
.AppF (.AppF fNatAddTyped (testTermTyped n)) (testTermTyped n)
-- Proposition: normal form of 2^(n+1) equals normal form of 2^n + 2^n
abbrev pow2DoubleEq (n : Nat) : Prop :=
(termf_normal_form (ctx := []) (testTermTyped (n + 1))).normalForm =
(termf_normal_form (ctx := []) (fNatAddTyped' (testTermTyped n) (testTermTyped n))).normalForm
section
open Lean Meta
def runPow2DoubleTest (n : Nat) : MetaM Unit := do
let goalType := mkApp (mkConst ``pow2DoubleEq) (mkNatLit n)
let goalMVar Meta.mkFreshExprMVar goalType
let mvarId := goalMVar.mvarId!
let startTime IO.monoNanosNow
let result Lean.Meta.Tactic.Cbv.cbvGoal mvarId
let endTime IO.monoNanosNow
let tacticMs := (endTime - startTime).toFloat / 1000000.0
if result.isSome then
throwError "cbv did not fully reduce pow2DoubleEq {n}"
let startTime IO.monoNanosNow
Meta.checkWithKernel goalMVar
let endTime IO.monoNanosNow
let kernelMs := (endTime - startTime).toFloat / 1000000.0
IO.println s!"pow2DoubleEq {n}: cbv {tacticMs} ms, kernel {kernelMs} ms"
def runPow2DoubleTests : MetaM Unit := do
IO.println "=== pow2DoubleEq Benchmark ==="
for n in [0, 1, 2, 3, 4, 5, 6] do
runPow2DoubleTest n
set_option maxRecDepth 10000 in
#eval runPow2DoubleTests
end