Zulip Chat Archive
Stream: new members
Topic: lambda calc formalization
cosindine (Apr 11 2025 at 23:30):
Hi all, recently I've been getting into type theory -- more specifically typed lambda calculi. I have quite a number of lambda calc text books at this point, and I figured I should go through one of them and write out some proofs in lean.
here is my current attempt at a representation:
namespace cosindine
universe u
def Set (α: Sort u) := α → Bool
def empty (α: Sort u) : Set α := λx↦ false
def union {α: Sort u} (a b: Set α) : Set α := λx↦ or (a x) (b x)
def intersect {α: Sort u} (a b: Set α) : Set α := λx↦ and (a x) (b x)
def complement {α: Sort u} (a: Set α) : Set α := λx↦ not (a x)
def ite {α: Sort u} (a: Bool) (t e: α) : α :=
match a with
| Bool.true => t
| Bool.false => e
def nat_eq := Nat.beq
def add := Nat.add
inductive Term where
| var : Nat → Term
| app : Term → Term → Term
| lam : Nat → Term → Term
mutual
unsafe def term_ts (t: Term) :=
match t with
| Term.var n => toString n
| Term.app a b => (term_ts_p a) ++ (term_ts b)
| Term.lam m n => "λ" ++ (toString m) ++ "." ++ (term_ts n)
unsafe def term_ts_p (t: Term) :=
match t with
| Term.var n => (toString n) ++ " "
| _ => "(" ++ (term_ts t) ++ ")"
end
instance : ToString Term where
toString t := unsafe term_ts t
def var (t: Term) : Set Nat :=
match t with
| Term.var n => nat_eq n
| Term.app a b => union (var a) (var b)
| Term.lam m n => union (nat_eq m) (var n)
def max_var (t: Term) : Nat :=
match t with
| Term.var n => n
| Term.app a b => max (max_var a) (max_var b)
| Term.lam m n => max m (max_var n)
def free_var (t: Term) : Set Nat :=
match t with
| Term.var n => nat_eq n
| Term.app a b => union (free_var a) (free_var b)
| Term.lam m n => λx↦ ite (nat_eq m x) False (free_var n x)
def rename_free (t: Term) (x y: Nat) : Term :=
match t with
| Term.var n => ite (nat_eq n x) (Term.var y) (Term.var n)
| Term.app a b => Term.app (rename_free a x y) (rename_free b x y)
| Term.lam m n => Term.lam m (ite (nat_eq m x) n (rename_free n x y))
unsafe def subst (t : Term) (x : Nat) (u : Term) : Term :=
match t with
| Term.var n => ite (nat_eq n x) u (Term.var n)
| Term.app a b => Term.app (subst a x u) (subst b x u)
| Term.lam m n =>
ite (nat_eq m x)
(Term.lam m n)
(ite (free_var u m)
(
let e1 := Nat.succ (add (max_var n) (max_var u))
Term.lam e1 (subst (rename_free n m e1) x u)
)
(Term.lam m (subst n x u)))
end consindine
excuse my lack of syntax, its mostly intentional.
it turns out that substitution is more annoying than you would expect!
image.png
my goal is to essentially prove the church rosser theorem and then move on to typed lambda calculi.
My questions are essentially:
should I bother with proving subst is well founded or just add a "gas/fuel" variable?
does the above seem reasonable to work with?
would it be worth representing terms in the de bruijn form?
is the curch rosser theorem provable for untyped lambda calc in lean (with the non-termination and such)?
Thanks!
Mario Carneiro (Apr 12 2025 at 00:40):
using de bruijn will fix the termination issue in subst
Mario Carneiro (Apr 12 2025 at 00:40):
but you can prove it in this form if you want
Mario Carneiro (Apr 12 2025 at 00:42):
your code is not a #mwe, I'm getting several missing references like nat_eq
and union
(also Set
but import Mathlib.Data.Set.Basic
fixes that)
cosindine (Apr 12 2025 at 00:45):
oop, probably should have checked >_<
so the set i've been using is
def Set (α: Sort u) := α → Bool
def empty (α: Sort u) : Set α := λx↦ false
def union {α: Sort u} (a b: Set α) : Set α := λx↦ or (a x) (b x)
def intersect {α: Sort u} (a b: Set α) : Set α := λx↦ and (a x) (b x)
def compliment {α: Sort u} (a: Set α) : Set α := λx↦ not (a x)
and nat_eq is
def nat_eq (a b: Nat) : Bool :=
match a, b with
| Nat.zero, Nat.zero => true
| Nat.succ n, Nat.zero => false
| Nat.zero, Nat.succ m => false
| Nat.succ n, Nat.succ m => nat_eq n m
Mario Carneiro (Apr 12 2025 at 00:47):
in other words you should put this at the top of your first post:
namespace cosindine
universe u
def Set (α: Sort u) := α → Bool
def empty (α: Sort u) : Set α := λx↦ False
def union {α: Sort u} (a b: Set α) : Set α := λx↦ or (a x) (b x)
def intersect {α: Sort u} (a b: Set α) : Set α := λx↦ and (a x) (b x)
def compliment {α: Sort u} (a: Set α) : Set α := λx↦ not (a x)
def nat_eq := Nat.beq
def add := Nat.add
Mario Carneiro (Apr 12 2025 at 00:47):
BTW compliment
is a very nice thing to say about someone, complement
is the set of elements not in a given one
Mario Carneiro (Apr 12 2025 at 00:48):
you're still missing add
, I've added that
Mario Carneiro (Apr 12 2025 at 01:00):
def size : Term → Nat
| Term.var _ => 1
| Term.app a b => size a + size b + 1
| Term.lam _ n => size n + 1
@[simp] theorem rename_free_size (t x y) : size (rename_free t x y) = size t := by
induction t <;> simp [*, rename_free, size] <;> split <;> simp [*, size]
def subst (t : Term) (x : Nat) (u : Term) : Term :=
match t with
| Term.var n => ite (nat_eq n x) u (Term.var n)
| Term.app a b => Term.app (subst a x u) (subst b x u)
| Term.lam m n =>
ite (nat_eq m x)
(Term.lam m n)
(ite (free_var u m)
(
let e1 := Nat.succ (add (max_var n) (max_var u))
Term.lam e1 (subst (rename_free n m e1) x u)
)
(Term.lam m (subst n x u)))
termination_by size t
decreasing_by all_goals simp [size] <;> omega
cosindine (Apr 12 2025 at 01:04):
huh doesn't work for me, but thats probably because i've defined ite as
def ite {α: Sort u} (a: Bool) (t e: α) : α :=
match a with
| Bool.true => t
| Bool.false => e
(i'll add it to the thing...)
<---- needs to start using libraries instead of reinventing everything
<---- has way to much fun reinventing everything
cosindine (Apr 12 2025 at 01:39):
alright I've fixed it with this beauty:
def size : Term → Nat
| Term.var _ => 1
| Term.app a b => Nat.succ (add (size a) (size b))
| Term.lam _ n => Nat.succ (size n)
-- hey man lean is based on CoC
@[simp] theorem rename_free_size (t x y) : size (rename_free t x y) = size t :=
match t with
| Term.var n =>
bool_cases (nat_eq n x)
(λp1 ↦
let e1 := Eq.congrFn (Eq.congrFn (Eq.congrArg ite p1) (Term.var y)) (Term.var n)
let e2 := Eq.trans (Eq.refl (rename_free (Term.var n) x y)) e1
let e3 := Eq.congrArg size e2
e3)
(λp1 ↦
let e1 := Eq.congrFn (Eq.congrFn (Eq.congrArg ite p1) (Term.var y)) (Term.var n)
let e2 := Eq.trans (Eq.refl (rename_free (Term.var n) x y)) e1
let e3 := Eq.congrArg size e2
e3)
| Term.app a b =>
let e1 := rename_free_size a x y
let e2 := rename_free_size b x y
Eq.fold add (Eq.fold add e1 e2) (Eq.refl 1)
| Term.lam m n =>
bool_cases (nat_eq m x)
(λp1 ↦
let e1 := Eq.congrFn (Eq.congrFn (Eq.congrArg ite p1) n) (rename_free n x y)
Eq.congrArg size (Eq.congrArg (Term.lam m) e1))
(λp1 ↦
let e1 := Eq.congrFn (Eq.congrFn (Eq.congrArg ite p1) n) (rename_free n x y)
let e2 := Eq.congrArg size (Eq.congrArg (Term.lam m) e1)
let e3 := rename_free_size n x y
Eq.trans e2 (Eq.congrArg Nat.succ e3))
-- yes i have a definition of add somewhere
@[simp] theorem add_is_add (a b: Nat) : Eq (add a b) (a + b) :=
match b with
| Nat.zero => rfl
| Nat.succ n => Eq.congrArg Nat.succ (add_is_add a n)
<---- still needs to figure out how to use tactics properly
it probably would have been easier to just send my entire file at this point :skull:
Mario Carneiro (Apr 12 2025 at 01:48):
<---- still needs to figure out how to use tactics properly
Pro tip, try using them
Mario Carneiro (Apr 12 2025 at 01:50):
Here's a version of the proof for your ite
:
@[simp] theorem rename_free_size (t x y) : size (rename_free t x y) = size t := by
induction t <;> simp [*, rename_free, size] <;> cases nat_eq .. <;> simp [*, ite, size]
Mario Carneiro (Apr 12 2025 at 01:51):
If you want to write everything from terms like a caveman then why are you using match
? That's a tactic you know :grinning_face_with_smiling_eyes:
Mario Carneiro (Apr 12 2025 at 01:51):
it desugars to an application of Term.recOn
cosindine (Apr 12 2025 at 01:52):
last time i tried that it complained about not being able to compile it inside of def's or smthn
or maybe that was just Term.rec?
don't remember...
Mario Carneiro (Apr 12 2025 at 02:00):
Here's a shorter term-only proof of rename_free_size
:
@[simp] theorem rename_free_size (t x y) : size (rename_free t x y) = size t :=
match t with
| .var _ =>
match (motive := ∀b, size (ite b ..) = 1) _ with
| true | false => rfl
| .app _ _ =>
congrArg (· + 1) <| congr (congrArg add (rename_free_size ..)) (rename_free_size ..)
| .lam _ _ =>
congrArg (· + 1) <|
match (motive := ∀b, size (ite b ..) = _) _ with
| true => rfl | false => rename_free_size ..
Mario Carneiro (Apr 12 2025 at 02:00):
last time i tried that it complained about not being able to compile it inside of def's or smthn
or maybe that was just Term.rec?
yeah, lean doesn't actually want you to use recursors like a caveman
cosindine (Apr 12 2025 at 02:01):
yeah for size
def size (t: Term) : Nat :=
Term.recOn t
(λa ↦ 1)
(λa b m1 m2↦ Nat.succ (add m1 m2))
(λm n m1↦ Nat.succ m1)
it complains with
code generator does not support recursor `Term.recOn` yet, consider using `match ... with` and/or structural recursion
Mario Carneiro (Apr 12 2025 at 02:01):
you can mark it as noncomputable
cosindine (Apr 12 2025 at 02:02):
but i want to compute with it :pleading:
Mario Carneiro (Apr 12 2025 at 02:02):
actually you probably don't care about computing size
Mario Carneiro (Apr 12 2025 at 02:02):
the default sizeOf
function is also noncomputable
Mario Carneiro (Apr 12 2025 at 02:02):
it's just used in termination arguments
Mario Carneiro (Apr 12 2025 at 02:03):
note that compute here means like #eval
, the kernel can compute with it according to the rules of CIC regardless
cosindine (Apr 12 2025 at 02:09):
i can't wait for this to be several hundred lines:
theorem double_subst {x y: Nat} {M N L: Term} (p1: Eq (nat_eq x y) false) (p2: Eq (free_var L x) false) :
Eq (subst (subst M x N) y L) (subst (subst M y L) x (subst N y L)) := sorry
2025-04-11-211108_755x153_scrot.png
cosindine (Apr 12 2025 at 02:36):
wait why doesn't this work?
theorem subst_var (n x u) : Eq (subst (Term.var n) x u) (ite (nat_eq n x) u (Term.var n)) := rfl
guess we using
theorem subst_var (n x u) : Eq (subst (Term.var n) x u) (ite (nat_eq n x) u (Term.var n)) := subst.eq_def (Term.var n) x u
Mario Carneiro (Apr 12 2025 at 04:01):
definitions by well founded recursion (i.e. any time you have to use termination_by
and/or decreasing_by
are generally not defeq to their bodies, because there is a proof under the hood that constructs the function via something like a fuel variable and then proves that you can get rid of it
cosindine (Apr 12 2025 at 04:23):
alright i've invested in a second tactic
theorem subst_no_free_var {t: Term} {x: Nat} (p1: Eq (free_var t x) false) (u: Term) : Eq (subst t x u) t :=
match t with
| Term.var n =>
let e1 := Eq.congrFn (Eq.congrFn (Eq.congrArg ite p1) u) (Term.var n)
Eq.trans (subst.eq_def _ _ _) e1
| Term.app a b =>
or_is_false p1
(λp2 p3 ↦
let e1 := subst_no_free_var p2 u
let e2 := subst_no_free_var p3 u
Eq.trans (subst.eq_def _ _ _) (Eq.fold Term.app e1 e2))
| Term.lam m n =>
bool_cases (nat_eq m x)
(λp2 ↦
let e1 := Eq.congrFn (Eq.congrFn (Eq.congrArg ite p2) _) _
Eq.trans (subst.eq_def _ _ _) e1)
(λp2 ↦
let e1 := (subst.eq_def (Term.lam m n) x u)
let e4 := Eq.congrFn (Eq.congrFn (Eq.congrArg ite p2) false) (free_var n x)
let e5 := Eq.trans (Eq.symm e4) p1
let e6 := subst_no_free_var e5 u
let e7 := (Eq.congrArg (Term.lam m) e6)
let e8 : (a b: Term) → Eq (ite false a b) b := λ a b ↦ rfl
let e9 : (b: Bool) → Eq (and false b) false := λ b ↦ match b with | true => rfl | false => rfl
let e10 := by simpa [e8, e9, p1, p2] using e1
Eq.trans e10 e7)
Last updated: May 02 2025 at 03:31 UTC