Skip to content

Commit 225c4b1

Browse files
m-owchenson2018ctchou
authored
feat: prove confluence for βη-reduction (#456)
Depends on #455. Adds $\eta$-reduction properties and proves confluence for $\beta \eta$-reduction. --------- Co-authored-by: Chris Henson <chrishenson.net@gmail.com> Co-authored-by: Chris Henson <46805207+chenson2018@users.noreply.github.com> Co-authored-by: Ching-Tsun Chou <chingtsun.chou@gmail.com>
1 parent 1aff460 commit 225c4b1

6 files changed

Lines changed: 221 additions & 6 deletions

File tree

Cslib.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ public import Cslib.Languages.LambdaCalculus.LocallyNameless.Untyped.Basic
9494
public import Cslib.Languages.LambdaCalculus.LocallyNameless.Untyped.Congruence
9595
public import Cslib.Languages.LambdaCalculus.LocallyNameless.Untyped.FullBeta
9696
public import Cslib.Languages.LambdaCalculus.LocallyNameless.Untyped.FullBetaConfluence
97+
public import Cslib.Languages.LambdaCalculus.LocallyNameless.Untyped.FullBetaEtaConfluence
9798
public import Cslib.Languages.LambdaCalculus.LocallyNameless.Untyped.FullEta
9899
public import Cslib.Languages.LambdaCalculus.LocallyNameless.Untyped.FullEtaConfluence
99100
public import Cslib.Languages.LambdaCalculus.LocallyNameless.Untyped.LcAt

Cslib/Languages/LambdaCalculus/LocallyNameless/Untyped/FullBeta.lean

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,22 @@ theorem redex_app_l_cong (redex : M ↠βᶠ M') (lc_N : LC N) : app M N ↠β
6363
theorem redex_app_r_cong (redex : M ↠βᶠ M') (lc_N : LC N) : app N M ↠βᶠ app N M' := by
6464
induction redex <;> grind
6565

66+
/- Single reduction `app M (fvar x) ⭢βᶠ N` implies reduction on `M` or a root beta step. -/
67+
@[scoped grind →]
68+
lemma invert_step_app_fvar (step : app M (fvar x) ⭢βᶠ N) :
69+
(∃ M', N = app M' (fvar x) ∧ M ⭢βᶠ M') ∨ (∃ M1, M = abs M1 ∧ N = M1 ^ fvar x) := by
70+
cases step
71+
case base h => cases h with | beta => exact .inr ⟨_, rfl, rfl⟩
72+
case appR step_M _ => exact .inl ⟨_, rfl, step_M⟩
73+
all_goals grind [cases Xi]
74+
6675
variable [HasFresh Var] [DecidableEq Var]
6776

6877
/-- The right side of a reduction is locally closed. -/
6978
@[scoped grind →]
7079
lemma step_lc_r (step : M ⭢βᶠ M') : LC M' := by
7180
induction step
72-
case' abs => constructor; assumption
81+
case abs => constructor; assumption
7382
all_goals grind
7483

7584
lemma steps_lc_or_rfl {M M' : Term Var} (redex : M ↠βᶠ M') : (LC M ∧ LC M') ∨ M = M' := by
@@ -88,6 +97,16 @@ lemma redex_subst_cong (s s' : Term Var) (x y : Var) (step : s ⭢βᶠ s') :
8897
s [ x := fvar y ] ⭢βᶠ s' [ x := fvar y ] :=
8998
redex_subst_cong_lc _ _ _ _ step (.fvar y)
9099

100+
/-- An β-reduction step does not introduce new free variables. -/
101+
lemma step_not_fv (step : M ⭢βᶠ N) (hw : w ∉ M.fv) : w ∉ N.fv := by
102+
induction step with
103+
| base h => cases h with | beta => grind [open_preserve_not_fvar]
104+
| abs =>
105+
have ⟨x, _⟩ := fresh_exists <| free_union [fv] Var
106+
have := open_close x
107+
grind [close_preserve_not_fvar, open_fresh_preserve_not_fvar]
108+
| _ => grind
109+
91110
/-- Abstracting then closing preserves a single reduction. -/
92111
lemma step_abs_close {x : Var} (step : M ⭢βᶠ M') : M⟦0 ↜ x⟧.abs ⭢βᶠ M'⟦0 ↜ x⟧.abs := by
93112
grind [Xi.abs ∅, redex_subst_cong]
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/-
2+
Copyright (c) 2026 Maximiliano Onofre Martínez. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Maximiliano Onofre Martínez
5+
-/
6+
7+
module
8+
9+
public import Cslib.Languages.LambdaCalculus.LocallyNameless.Untyped.FullBetaConfluence
10+
public import Cslib.Languages.LambdaCalculus.LocallyNameless.Untyped.FullEtaConfluence
11+
12+
@[expose] public section
13+
14+
set_option linter.unusedDecidableInType false
15+
16+
/-! # βη-Confluence for the λ-calculus
17+
18+
## Reference
19+
20+
* [T. Nipkow, *More Church-Rosser Proofs (in Isabelle/HOL)*][Nipkow2001]
21+
22+
-/
23+
24+
namespace Cslib
25+
26+
universe u
27+
28+
variable {Var : Type u}
29+
variable [HasFresh Var] [DecidableEq Var]
30+
31+
namespace LambdaCalculus.LocallyNameless.Untyped.Term
32+
33+
open Relation
34+
35+
/-- Full βη-reduction. -/
36+
@[reduction_sys "βηᶠ"]
37+
abbrev FullBetaEta : Term Var → Term Var → Prop := FullBeta ⊔ FullEta
38+
39+
open FullEta FullBeta in
40+
/-- η-reduction and β-reduction strongly commute. -/
41+
lemma stronglyCommute_eta_beta : StronglyCommute (@FullEta Var) FullBeta := by
42+
intro x y₁ y₂ h₁ st_beta
43+
induction st_beta generalizing y₁
44+
case base h1_b =>
45+
cases h1_b
46+
case beta M N _ _ =>
47+
cases h₁
48+
case base h => cases h
49+
case appL v _ _ =>
50+
use M ^ v
51+
grind [step_open_cong_r]
52+
case appR u st_eta_absM _ =>
53+
have := step_lc_r st_eta_absM
54+
cases st_eta_absM
55+
case base h => use (disch := grind) app u N
56+
case abs M_eta xs _ =>
57+
have ⟨_, hz⟩ := fresh_exists (xs ∪ N.fv ∪ M.fv ∪ M_eta.fv)
58+
use M_eta ^ N
59+
grind [step_subst_cong_l]
60+
case appL Z _ N _ _ ih =>
61+
cases h₁
62+
case base h => cases h
63+
case appL _ _ st =>
64+
use app Z (ih st).choose
65+
grind [FullEta.redex_app_r_cong]
66+
case appR z_red _ _ => use (disch := grind) app z_red N
67+
case appR M _ Z _ _ ih =>
68+
cases h₁
69+
case base h => cases h
70+
case appL z_red _ _ => use (disch := grind) app Z z_red
71+
case appR _ st _ =>
72+
use app (ih st).choose M
73+
grind [FullEta.redex_app_l_cong]
74+
case abs M N xs st_body_beta ih =>
75+
cases h₁
76+
case base h_eta =>
77+
cases h_eta with | eta =>
78+
have ⟨w, _⟩ := fresh_exists <| free_union [fv] Var
79+
have st_beta_w : app y₁ (fvar w) ⭢βᶠ N ^ fvar w := by grind [st_body_beta w]
80+
rcases invert_step_app_fvar st_beta_w with ⟨u', _, st_u⟩ | ⟨u1, _, _⟩
81+
· use u'
82+
grind [open_eq_app ?_ (step_not_fv st_u ?_)]
83+
· use abs u1
84+
grind [open_injective w N u1]
85+
case abs S ys st_body_eta =>
86+
have ⟨w, _⟩ := fresh_exists <| free_union [fv] Var
87+
obtain ⟨K, h_beta, h_eta⟩ := ih w (by grind) (st_body_eta w (by grind))
88+
use abs (K ^* w)
89+
constructor
90+
· cases h_beta with
91+
| refl => grind [open_close]
92+
| single => exact .single (Xi.abs {w} (by grind [FullBeta.redex_subst_cong]))
93+
· rw [open_close w N 0 (by grind)]
94+
exact FullEta.redex_abs_close h_eta (FullBeta.step_lc_r (st_body_beta w (by grind)))
95+
96+
open Commute in
97+
/-- βη-reduction is confluent. -/
98+
theorem confluent_beta_eta : Confluent (@FullBetaEta Var) := by
99+
apply join_confluent
100+
· exact confluence_beta
101+
· exact stronglyConfluent_eta.toConfluent
102+
exact symmetric stronglyCommute_eta_beta.toCommute
103+
104+
end LambdaCalculus.LocallyNameless.Untyped.Term
105+
106+
end Cslib

Cslib/Languages/LambdaCalculus/LocallyNameless/Untyped/FullEta.lean

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ public import Cslib.Languages.LambdaCalculus.LocallyNameless.Untyped.Congruence
1212

1313
public section
1414

15+
set_option linter.unusedDecidableInType false
16+
1517
/-! # η-reduction for the λ-calculus -/
1618

1719
namespace Cslib
@@ -24,13 +26,13 @@ namespace LambdaCalculus.LocallyNameless.Untyped.Term
2426

2527
/-- A single η-reduction step. -/
2628
@[scoped grind]
27-
inductive BaseEta : Term Var → Term Var → Prop
29+
inductive Eta : Term Var → Term Var → Prop
2830
/-- The eta rule: λx. M x ⟶ M, provided x is not free in M. -/
29-
| eta : LC M → BaseEta (abs (app M (bvar 0))) M
31+
| eta : LC M → Eta (abs (app M (bvar 0))) M
3032

3133
/-- Full η-reduction, defined as the congruence closure of the base η-rule. -/
3234
@[reduction_sys "ηᶠ"]
33-
abbrev FullEta : Term Var → Term Var → Prop := Xi BaseEta
35+
abbrev FullEta : Term Var → Term Var → Prop := Xi Eta
3436

3537
namespace FullEta
3638

@@ -42,6 +44,14 @@ lemma step_lc_r (step : M ⭢ηᶠ M') : LC M' := by
4244
refine Xi.step_lc_r ?_ step
4345
grind
4446

47+
/-- Left congruence rule for application in multiple reduction. -/
48+
theorem redex_app_l_cong (redex : M ↠ηᶠ M') (lc_N : LC N) : app M N ↠ηᶠ app M' N := by
49+
induction redex <;> grind
50+
51+
/-- Right congruence rule for application in multiple reduction. -/
52+
theorem redex_app_r_cong (redex : M ↠ηᶠ M') (lc_N : LC N) : app N M ↠ηᶠ app N M' := by
53+
induction redex <;> grind
54+
4555
/- Single reduction `app M (fvar x) ⭢ηᶠ N` implies `N = app M' (fvar x)` for some M' -/
4656
@[scoped grind →]
4757
lemma invert_step_app_fvar (step : (app M (fvar x)) ⭢ηᶠ N) :
@@ -69,6 +79,65 @@ lemma eta_subst_fvar {x y : Var} (step : M ⭢ηᶠ M') : M [ x := fvar y ] ⭢
6979
| abs => grind [Xi.abs <| free_union Var]
7080
| _ => grind
7181

82+
/-- Abstracting then closing preserves a single η-reduction step. -/
83+
lemma step_abs_close {x} (step : M ⭢ηᶠ M') (lc_M : LC M) : (M ^* x).abs ⭢ηᶠ (M' ^* x).abs := by
84+
grind [Xi.abs ∅]
85+
86+
/-- Abstracting then closing preserves multiple reductions. -/
87+
lemma redex_abs_close {x} (steps : M ↠ηᶠ M') (lc_M : LC M) : (M ^* x).abs ↠ηᶠ (M' ^* x).abs := by
88+
induction steps using Relation.ReflTransGen.head_induction_on
89+
case refl => exact .refl
90+
case head b c st_bc _ ih => exact .head (step_abs_close st_bc lc_M) (ih (step_lc_r st_bc))
91+
92+
/-- Multiple reduction of opening implies multiple reduction of abstraction. -/
93+
theorem redex_abs_cong {M M' : Term Var} (xs : Finset Var)
94+
(cofin : ∀ x ∉ xs, (M ^ fvar x) ↠ηᶠ M' ^ fvar x) (lc_M : LC M.abs) :
95+
M.abs ↠ηᶠ M'.abs := by
96+
cases lc_M
97+
case abs L hL =>
98+
have ⟨x, _⟩ := fresh_exists <| free_union [fv] Var
99+
rw [open_close x M 0, open_close x M' 0]
100+
all_goals grind [redex_abs_close (x := x) (cofin x ?_) (hL x ?_)]
101+
102+
/- `t ⭢ηᶠ t'` implies `s [ x := t ] ↠ηᶠ s [ x := t' ]`. -/
103+
lemma step_subst_cong_r {x : Var} (s t t' : Term Var) (st : t ⭢ηᶠ t') (lc_s : LC s) (lc_t : LC t) :
104+
s [ x := t ] ↠ηᶠ s [ x := t' ] := by
105+
induction lc_s generalizing t t' with
106+
| fvar => grind
107+
| app hl hr ih_l ih_r =>
108+
trans
109+
· exact redex_app_l_cong (ih_l t t' st lc_t) (subst_lc hr lc_t)
110+
· exact redex_app_r_cong (ih_r t t' st lc_t) (subst_lc hl (step_lc_r st))
111+
| abs L body h_lc_body ih =>
112+
apply redex_abs_cong (L ∪ {x})
113+
· intro z
114+
grind =>
115+
have : (body ^ fvar z)[x := t] ↠ηᶠ (body ^ fvar z)[x := t']
116+
finish
117+
· exact subst_lc (LC.abs L body h_lc_body) lc_t
118+
119+
/- `steps_subst_cong_r` can be generalized to multiple reductions `t ↠ηᶠ t'`. -/
120+
lemma steps_subst_cong_r {x : Var} (s t t' : Term Var) (st : t ↠ηᶠ t') (lc_s : LC s) (lc_t : LC t) :
121+
s [ x := t ] ↠ηᶠ s [ x := t' ] := by
122+
induction st using Relation.ReflTransGen.head_induction_on
123+
case refl => rfl
124+
case head _ _ st _ ih => exact .trans (step_subst_cong_r s _ _ st lc_s lc_t) (ih (step_lc_r st))
125+
126+
/- `t ⭢ηᶠ t'` implies `s ^ t ↠ηᶠ s ^ t'`. -/
127+
lemma step_open_cong_r {s t t' : Term Var} (lc_s : LC s.abs) (lc_t : LC t) (step : t ⭢ηᶠ t') :
128+
(s ^ t) ↠ηᶠ s ^ t' := by
129+
cases lc_s
130+
case abs L hL =>
131+
have ⟨x, _⟩ := fresh_exists <| free_union [fv] Var
132+
grind [step_subst_cong_r (x := x) (s ^ fvar x) t t' step (hL x ?_) lc_t]
133+
134+
/- `steps_open_cong_r` can be generalized to multiple reductions `t ↠ηᶠ t'`. -/
135+
lemma steps_open_cong_r {s t t' : Term Var} (lc_s : LC s.abs) (lc_t : LC t) (steps : t ↠ηᶠ t') :
136+
(s ^ t) ↠ηᶠ s ^ t' := by
137+
induction steps using Relation.ReflTransGen.head_induction_on
138+
case refl => rfl
139+
case head _ _ st _ ih => exact .trans (step_open_cong_r lc_s lc_t st) (ih (step_lc_r st))
140+
72141
/- Closing a sequence of η-reduction steps over a fresh variable preserves the steps. -/
73142
open Relation in
74143
lemma close_eta_steps (hx_M : x ∉ M.fv) (st_M : ReflGen FullEta (M ^ fvar x) N) :
@@ -78,6 +147,21 @@ lemma close_eta_steps (hx_M : x ∉ M.fv) (st_M : ReflGen FullEta (M ^ fvar x) N
78147
| single st =>
79148
exact .single (Xi.abs {x} (by grind))
80149

150+
/- `s ⭢ηᶠ s'` implies `s [ x := N ] ⭢ηᶠ s' [ x := N ]`. -/
151+
lemma step_subst_cong_l {x : Var} (s s' N : Term Var) (step : s ⭢ηᶠ s') (lc_N : LC N) :
152+
s [ x := N ] ⭢ηᶠ s' [ x := N ] := by
153+
induction step
154+
case base h => cases h with | eta lc => exact Xi.base (.eta (subst_lc lc lc_N))
155+
case abs => grind [Xi.abs <| free_union Var, subst_open_var]
156+
all_goals grind
157+
158+
/- `steps_subst_cong_l` can be generalized to multiple reductions `s ↠ηᶠ s'`. -/
159+
lemma steps_subst_cong_l {x : Var} (s s' N : Term Var) (steps : s ↠ηᶠ s') (lc_N : LC N) :
160+
s [ x := N ] ↠ηᶠ s' [ x := N ] := by
161+
induction steps with
162+
| refl => rfl
163+
| tail _ step ih => grind [step_subst_cong_l]
164+
81165
end LambdaCalculus.LocallyNameless.Untyped.Term.FullEta
82166

83167
end Cslib

Cslib/Languages/LambdaCalculus/LocallyNameless/Untyped/FullEtaConfluence.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ lemma stronglyConfluent_eta : StronglyConfluent (@FullEta Var) := by
4646
have ⟨w, _⟩ := fresh_exists <| free_union [fv] Var
4747
have ⟨M', _, _⟩ := invert_step_app_fvar <| st_body w <| by grind
4848
use M'
49-
grind [→ BaseEta.eta, step_not_fv, open_eq_app]
49+
grind [→ Eta.eta, step_not_fv, open_eq_app]
5050
case appL Z _ N _ _ ih =>
5151
cases h₂
5252
case base h => cases h
@@ -64,7 +64,7 @@ lemma stronglyConfluent_eta : StronglyConfluent (@FullEta Var) := by
6464
have ⟨w, _⟩ := fresh_exists <| free_union [fv] Var
6565
have ⟨M', _, _⟩ := invert_step_app_fvar <| st_M_N w (by grind)
6666
use M'
67-
grind [→ BaseEta.eta, step_not_fv, open_eq_app]
67+
grind [→ Eta.eta, step_not_fv, open_eq_app]
6868
case abs N _ st_M_N =>
6969
have ⟨x, _⟩ := fresh_exists <| free_union [fv] Var
7070
have ⟨w, _⟩ := ih x (by grind) (z := N ^ fvar x) (st_M_N x (by grind))

Cslib/Languages/LambdaCalculus/LocallyNameless/Untyped/Properties.lean

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ lemma open_fresh_preserve_not_fvar {k x y} (m : Term Var) (nmem : x ∉ m.fv) (n
6363
x ∉ (m⟦k ↝ fvar y⟧).fv := by
6464
induction m generalizing k <;> grind
6565

66+
/-- Opening preserves free variables. -/
67+
lemma open_preserve_not_fvar {k x} (m n : Term Var) (nmem_m : x ∉ m.fv) (nmem_n : x ∉ n.fv) :
68+
x ∉ (m⟦k ↝ n⟧).fv := by
69+
induction m generalizing k <;> grind
70+
6671
/-- Substitution preserves free variables. -/
6772
lemma subst_preserve_not_fvar {x y : Var} (m n : Term Var) (nmem : x ∉ m.fv ∪ n.fv) :
6873
x ∉ (m [y := n]).fv := by

0 commit comments

Comments
 (0)