Skip to content

Commit ba0fb9d

Browse files
feat: An Alternative Definition of the Locally Closed Term of Locally Nameless LC Using LcAt (#336)
In [A. Chargueraud, The Locally Nameless Representation](https://chargueraud.org/research/2009/ln/main.pdf), which is the primary reference of implementation, several different definitions of “locally closed” are proposed. One of them is using "lc_at". ``` lc_at 0 M ↔ M.LC ``` This is somewhat useful in proving strong normalizing property and maybe other theorems related to locally closed terms. Also it is straightforward to defining an algorithm later to decide whether certain term is locally closed on not. --------- Co-authored-by: Chris Henson <chrishenson.net@gmail.com>
1 parent 3be3225 commit ba0fb9d

2 files changed

Lines changed: 90 additions & 0 deletions

File tree

Cslib.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public import Cslib.Languages.LambdaCalculus.LocallyNameless.Stlc.Safety
8282
public import Cslib.Languages.LambdaCalculus.LocallyNameless.Untyped.Basic
8383
public import Cslib.Languages.LambdaCalculus.LocallyNameless.Untyped.FullBeta
8484
public import Cslib.Languages.LambdaCalculus.LocallyNameless.Untyped.FullBetaConfluence
85+
public import Cslib.Languages.LambdaCalculus.LocallyNameless.Untyped.LcAt
8586
public import Cslib.Languages.LambdaCalculus.LocallyNameless.Untyped.Properties
8687
public import Cslib.Languages.LambdaCalculus.Named.Untyped.Basic
8788
public import Cslib.Logics.HML.Basic
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/-
2+
Copyright (c) 2026 Elimia (Sehun Kim). All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Elimia (Sehun Kim)
5+
-/
6+
7+
module
8+
9+
public import Cslib.Languages.LambdaCalculus.LocallyNameless.Untyped.Properties
10+
11+
@[expose] public section
12+
13+
/-!
14+
15+
Alternative Definitions for LC:
16+
17+
This module defines `LcAt k M`, a more general definition of local closure. When k = 0, this is
18+
equivalent to `LC`, as shown in `lcAt_iff_LC`.
19+
20+
-/
21+
22+
namespace Cslib.LambdaCalculus.LocallyNameless.Untyped.Term
23+
24+
universe u
25+
26+
variable {Var : Type u}
27+
28+
/-- `LcAt k M` is satisfied when all bound indices of M are smaller than `k`. -/
29+
@[simp, scoped grind =]
30+
def LcAt (k : ℕ) : Term Var → Prop
31+
| bvar i => i < k
32+
| fvar _ => True
33+
| app t₁ t₂ => LcAt k t₁ ∧ LcAt k t₂
34+
| abs t => LcAt (k + 1) t
35+
36+
/-- `depth` counts the maximum number of the lambdas that are enclosing variables. -/
37+
@[simp, scoped grind =]
38+
def depth : Term Var → ℕ
39+
| bvar _ => 0
40+
| fvar _ => 0
41+
| app t₁ t₂ => max (depth t₁) (depth t₂)
42+
| abs t => depth t + 1
43+
44+
@[elab_as_elim]
45+
protected lemma ind_on_depth (P : Term Var → Prop) (bvar : ∀ i, P (bvar i)) (fvar : ∀ x, P (fvar x))
46+
(app : ∀ M N, P M → P N → P (app M N)) (abs : ∀ M, P M → (∀ N, N.depth ≤ M.depth → P N) → P M.abs)
47+
(M : Term Var) : P M := by
48+
have h {d : ℕ} {M : Term Var} (p : M.depth ≤ d) : P M := by
49+
induction d generalizing M with
50+
| zero => induction M <;> grind
51+
| succ =>
52+
induction M with
53+
| abs M' => apply abs M' <;> grind
54+
| _ => grind [sup_le_iff]
55+
exact h M.depth.le_refl
56+
57+
/-- The depth of the lambda expression doesn't change by opening at i-th bound variable
58+
for some free variable. -/
59+
@[simp, scoped grind =]
60+
lemma depth_openRec_fvar_eq_depth (M : Term Var) (x : Var) (i : ℕ) :
61+
(M⟦i ↝ fvar x⟧).depth = M.depth := by
62+
induction M generalizing i <;> grind
63+
64+
/-- The depth of the lambda expression doesn't change by opening for some free variable. -/
65+
theorem depth_open_fvar_eq_depth (M : Term Var) (x : Var) : depth (M ^ fvar x) = depth M :=
66+
depth_openRec_fvar_eq_depth M x 0
67+
68+
/-- Opening for some free variable at i-th bound variable, increments `LcAt`. -/
69+
@[simp, scoped grind =]
70+
theorem lcAt_openRec_fvar_iff_lcAt (M : Term Var) (x : Var) (i : ℕ) :
71+
LcAt i (M⟦i ↝ fvar x⟧) ↔ LcAt (i + 1) M := by
72+
induction M generalizing i <;> grind
73+
74+
/-- Opening for some free variable is locally closed if and only if `M` is `LcAt 1`. -/
75+
theorem lcAt_open_fvar_iff_lcAt (M : Term Var) (x : Var) : LcAt 0 (M ^ fvar x) ↔ LcAt 1 M :=
76+
lcAt_openRec_fvar_iff_lcAt M x 0
77+
78+
/-- `M` is `LcAt 0` if and only if `M` is locally closed. -/
79+
theorem lcAt_iff_LC (M : Term Var) [HasFresh Var] : LcAt 0 M ↔ M.LC := by
80+
induction M using LambdaCalculus.LocallyNameless.Untyped.Term.ind_on_depth with
81+
| abs =>
82+
constructor
83+
· grind [LC.abs ∅]
84+
· intros h2
85+
rcases h2 with ⟨⟩|⟨L,_,_⟩
86+
grind [fresh_exists L]
87+
| _ => grind [cases LC]
88+
89+
end Cslib.LambdaCalculus.LocallyNameless.Untyped.Term

0 commit comments

Comments
 (0)