Zulip Chat Archive
Stream: Is there code for X?
Topic: lambda_ite
Joachim Breitner (Nov 22 2024 at 08:59):
We have theorems like docs#apply_ite and docs#ite_apply. Are we missing something for pushing a lambda inside the branches of an ite? I.e. to do the following rewrite without using split
?
example (c : Bool) (t e : Nat → Prop):
(fun x => ite c (t x) (e x)) = (ite c (fun x => t x) (fun x => e x)) := by
split <;> simp
Or does that somehow follow from the existing ite
lemmas?
Can we maybe even write a single apply_ite-like lemma that can push an arbitrary context inside the branches, even if it binds one or more variables (assuming these variables do not appear in the condition)?
(Somehow I feel like I have a knot in my head)
Martin Dvořák (Nov 22 2024 at 09:17):
I am not aware of any existing lemma that does that.
BTW, a small refactoring:
example (c : Bool) (t e : Nat → Prop):
(fun x => ite c (t x) (e x)) = ite c t e := by
split <;> rfl
Yaël Dillies (Nov 22 2024 at 10:49):
Joachim Breitner said:
Can we maybe even write a single apply_ite-like lemma that can push an arbitrary context inside the branches, even if it binds one or more variables (assuming these variables do not appear in the condition)?
I've been wanting a pair norm_ite
/push_ite
of tactics for a while now
Joachim Breitner (Nov 22 2024 at 11:29):
These tactics may come out of https://github.com/leanprover/lean4/pull/5923 (for these purposes I’d argue it makes sense to treat ite
like a match
statement.
Joachim Breitner (Nov 22 2024 at 11:41):
Ok, I can answer my own question: The rewrite that I wanted does follow from ite_apply
:
example (c : Prop) [Decidable c] (t e : Nat → Prop):
(fun x => ite c (t x) (e x)) = (ite c (fun x => t x) (fun x => e x)) :=
funext (fun x => (ite_apply c t e x).symm)
(A more detailed proof would do the appropriate eta-expansion and contraction, but lean does figure that out.)
In fact, the apply_ite
theorem is sufficient to infer the other ones:
import Mathlib
universe u v
variable {α : Sort u} {β : Sort v}
axiom m : (t e : α) → α
axiom apply_m (f : α → β) (t e : α) : f (m t e) = m (f t) (f e)
theorem m_apply (t e : α → β) (x : α) : (m t e) x = m (t x) (e x) :=
apply_m (f := (· x)) t e
theorem lam_f (t e : α → β) : (fun x => m (t x) (e x)) = m t e :=
funext (fun x => (m_apply t e x).symm)
This means that every operation (ite
, match
-statements) that admit an apply_foo
lemma can be lifted out of (at least non-dependent) contexts with a suitable tactic. I guess this is a pretty good characterization of a “tail-position”, e.g. when recognizing tail-recursive functions (e.g. for lean4#3119).
Joachim Breitner (Nov 22 2024 at 12:19):
And more interesting lemmas follow just from apply_m
:
theorem m_self (e : α) : m e e = e :=
(apply_m (fun _ => e) () ()).symm
theorem m_split (P : α → Prop) (t e : α) (ht : P t) (he : P e) : P (m t e) := by
rw [apply_m (f := (P ·))]
rw [eq_true ht]
rw [eq_true he]
rw [m_const]
trivial
theorem apply_m₂ (t1 e1 : α) (t2 e2 : β) (f : α → β → γ) :
f (m t1 e1) (m t2 e2) = m (f t1 t2) (f e1 e2) :=
calc f (m t1 e1) (m t2 e2)
_ = f (m (PProd.fst ⟨t1,t2⟩) (PProd.fst ⟨e1,e2⟩)) (m (PProd.snd ⟨t1,t2⟩) (PProd.snd ⟨e1,e2⟩)) := rfl
_ = f (PProd.fst (m ⟨t1,t2⟩ ⟨e1,e2⟩)) (m (PProd.snd ⟨t1,t2⟩) (PProd.snd ⟨e1,e2⟩)) := by rw [← apply_m (f := PProd.fst)]
_ = f (PProd.fst (m ⟨t1,t2⟩ ⟨e1,e2⟩)) (PProd.snd (m ⟨t1,t2⟩ ⟨e1,e2⟩)) := by rw [← apply_m (f := PProd.snd)]
_ = m (f t1 t2) (f e1 e2) := apply_m (fun (x : PProd α β) => f x.1 x.2) _ _
Joachim Breitner (Nov 22 2024 at 12:47):
Especially the last one I wasn’t sure if it was derivable, and find it particularly interesting: This means that if you have multiple instances of the same “match” expressions (that is, same target, but diffferent arms, different motives) in the goal, you can lift them out together and end up having only a single match expression.
Joachim Breitner (Nov 22 2024 at 14:02):
Oh, even this can be proven purely from apply_m
:
theorem m_m (e1 e2 e3 : α) : m (m e1 e2) e3 = m e1 e3 :=
calc m (m e1 e2) e3
_ = (fun x => m x e3) (m e1 e2) := rfl
_ = (fun a b => a b) (fun x => m x e3) (m e1 e2) := rfl
_ = (fun a b => a b) (m (fun x => x) (fun x => e3)) (m e1 e2) := by rw [lam_m]
_ = m ((fun a b => a b) (fun x => x) e1) ((fun a b => a b) (fun x => e3) e2) := apply₂_m (fun x => x) (fun x => e3) e1 e2 (fun a b => a b)
_ = m e1 e3 := rfl
Daniel Weber (Nov 22 2024 at 15:26):
You can prove
theorem m_val (a b : α) : m a b = if m true false then a else b := by
simpa using (apply_m (fun x ↦ if x then a else b) true false).symm
i.e. m
is either constant left or constant right, so I'd expect this
Joachim Breitner (Nov 22 2024 at 15:29):
Heh, good point. But I hope all of this generalizes to more complex match
statements, with multiple alternatives, bound variables in the alternative etc.
In (maybe) other words: Is apply_m
sufficient to prove all the things that in other settings you’d get from parametricity?
Joachim Breitner (Nov 22 2024 at 16:06):
Right now I’m puzzling about this: From
axiom apply_m (f : α → β) (t e : α) : f (m t e) = m (f t) (f e)
I can derive
theorem apply_m' (f : (α → α → α) → β) : f m = m (f (fun t e => t)) (f (fun t e => e)) := by
change f (fun t e => m t e) = m (f (fun t e => t)) (f (fun t e => e))
conv => lhs; arg 1; ext t; apply lam_m
conv => lhs; arg 1; apply lam_m
apply apply_m f
but can I also do
theorem apply_m'' (f : (∀ {α}, α → α → α) → β) : f m = m (f (fun t e => t)) (f (fun t e => e)) := by
sorry
(note the changed argument of f
), or should that be the “more basic” axiom for a “match-like function”?
Joachim Breitner (Nov 22 2024 at 16:14):
Hmm, but even if I had that, I had hoped I could prove theorems like
theorem m_strong_split (P : α → Prop) {t e : α} :
(ht : (∀ (α : Sort u) (t e : α), m t e = t) → P t) →
(he : (∀ (α : Sort u) (t e : α), m t e = e) → P e) →
P (m t e) := by
theorem m_congr (t1 t2 e1 e2 : α)
(ht : (∀ (α : Sort u) (t e : α), m t e = t) → t1 = t2)
(he : (∀ (α : Sort u) (t e : α), m t e = e) → e1 = e2) :
m t1 e1 = m t2 e2 := by
and I almost can, but then the universes get in the way.
Last updated: May 02 2025 at 03:31 UTC