Zulip Chat Archive
Stream: new members
Topic: What's an idiomatic way to avoid defeq abuse here?
Dan Abramov (Jul 23 2025 at 19:59):
I'm working on some proofs related to equivalence of Mathlib functions and Sets of Kuratowski pairs.
My current setup (and a few theorems) looks like this.
import Mathlib.Tactic
def kpair (a b : Nat) : Set (Set Nat) := {{a}, {a, b}}
lemma kpair_injective : ∀ {a₁ b₁ a₂ b₂}, kpair a₁ b₁ = kpair a₂ b₂ → a₁ = a₂ ∧ b₁ = b₂ := by
intro a1 b1 a2 b2 h
unfold kpair at h
constructor
· contrapose! h
intro hs
rw [Set.ext_iff] at hs
specialize hs {a1}
simp only [Set.mem_insert_iff, Set.mem_singleton_iff, true_or] at hs
rw [← Set.pair_eq_singleton a1, Set.pair_eq_pair_iff] at hs
simp_all
contrapose! h
intro hs
simp only [Set.pair_eq_pair_iff] at hs
rcases hs with (_ | ⟨h1, h2⟩)
· aesop
rw [← Set.pair_eq_singleton a1, Set.pair_eq_pair_iff] at h1
rw [← Set.pair_eq_singleton a2, Set.pair_eq_pair_iff] at h2
simp_all
def IsRelation (g: (Set (Set (Set Nat)))) : Prop := ∀ p ∈ g, ∃ x y, p = kpair x y
def IsDefined (g: (Set (Set (Set Nat)))) : Prop := ∀ x, ∃ y, kpair x y ∈ g
def IsFunctional (g: (Set (Set (Set Nat)))) : Prop := ∀ {x y₁ y₂}, (kpair x y₁) ∈ g → (kpair x y₂) ∈ g → y₁ = y₂
def IsGraph (g: (Set (Set (Set Nat)))) : Prop := IsRelation g ∧ IsDefined g ∧ IsFunctional g
lemma graph_defined (hg : IsGraph g) (x: Nat) : ∃ y, kpair x y ∈ g := by
unfold IsGraph IsDefined at hg
exact hg.2.1 x
lemma graph_functional (hg : IsGraph g) (x: Nat) :
(kpair x y1) ∈ g → (kpair x y2) ∈ g → y1 = y2 := by
unfold IsGraph IsFunctional at hg
exact hg.2.2
def funToPairs (f : Nat → Nat) : Set (Set (Set Nat)) :=
{kpair a (f a) | a : Nat}
def funToPairs_spec (f : Nat → Nat) : p ∈ funToPairs f ↔ ∃ x, kpair x (f x) = p := by
unfold funToPairs
simp
def funToPairs_spec' (f : Nat → Nat) : kpair x (f x) ∈ funToPairs f := by
unfold funToPairs
simp
noncomputable def pairsToFun (hg : IsGraph g) (x : Nat) :=
(graph_defined hg x).choose
lemma pairsToFun_spec (hg : IsGraph g) (x : Nat) : kpair x (pairsToFun hg x) ∈ g :=
(graph_defined hg x).choose_spec
theorem funToPairs_IsGraph (f : Nat → Nat) : IsGraph (funToPairs f) := by
unfold IsGraph
constructor
· unfold IsRelation
intro p hp
obtain ⟨x, hx⟩ := (funToPairs_spec f).mp hp
use x, (f x), hx.symm
constructor
· unfold IsDefined
intro x
use f x
apply funToPairs_spec'
unfold IsFunctional
intro x y1 y2 h1 h2
obtain ⟨x1, hx1⟩ := (funToPairs_spec f).mp h1
obtain ⟨x2, hx2⟩ := (funToPairs_spec f).mp h2
obtain ⟨rfl, rfl⟩ := kpair_injective hx1
obtain ⟨rfl, rfl⟩ := kpair_injective hx2
rfl
theorem pairsToFun_funToPairs (f : Nat → Nat) : pairsToFun (funToPairs_IsGraph f) = f := by
set hg := funToPairs_IsGraph f
ext x
apply graph_functional hg
· apply pairsToFun_spec
apply funToPairs_spec'
What I'm hoping to get feedback on is how far I am from idiomatic code. I've previously been doing a lot more unfolding in my proofs but as a result they were very messy and difficult to follow. I've now extracted important props into definitions (IsRelation, IsGraph, etc). My questions are:
- Is this still defeq abuse? Should I keep going further and replace
unfold IsRelationand such with lemmas? Should they mirror existing Props 1:1 or is there something smarter I could do? - Are these
funToPairs_specandfunToPairs_spec'weird? I've defined them next tofunToPairsbut not sure if they're too trivial and if the names make sense. - Am I using explicit/implicit parameters well, or could signatures be improved?
- Overall, if you were reworking this, is there anything you'd change at a high level? Maybe replacing Props with
structures? Or something else?
Thanks!
Aaron Liu (Jul 23 2025 at 20:09):
Dan Abramov said:
- Are these
funToPairs_specandfunToPairs_spec'weird? I've defined them next tofunToPairsbut not sure if they're too trivial and if the names make sense.
Quite the opposite, this is how most definitions in mathlib work. What you are supposed to do is define your definition, and then (in the same file preferably) you should prove a bunch of lemmas about your definition so you never have to unfold it again. In practice this is not what happens, and being able to unfold some definitions is useful, for example docs#Set.mem_inter_iff, docs#Set.mem_compl_iff, and docs#Set.subset_def are theorems I rarely use because they're defeqs and I can just (implicity) unfold some definitions.
Dan Abramov (Jul 23 2025 at 21:31):
If I were to tuck away the props like this:
structure IsGraph (g : Set (Set (Set Nat))) where
relation : ∀ p ∈ g, ∃ x y, p = kpair x y
defined : ∀ x, ∃ y, kpair x y ∈ g
functional : ∀ {x y₁ y₂}, kpair x y₁ ∈ g → kpair x y₂ ∈ g → y₁ = y₂
is there still a benefit to having lemmas like
lemma graph_relation (hg : IsGraph g) : ∀ p ∈ g, ∃ x y, kpair x y = p := by
intro p hp
obtain ⟨x, ⟨y, rfl⟩⟩ := hg.relation p hp
use x, y
lemma graph_defined (hg : IsGraph g) (x: Nat) : ∃ y, kpair x y ∈ g := by
exact hg.defined x
lemma graph_functional (hg : IsGraph g) (x: Nat) :
(kpair x y1) ∈ g → (kpair x y2) ∈ g → y1 = y2 := by
exact hg.functional
?
Or are structure fields generally considered public API?
Kyle Miller (Jul 23 2025 at 21:38):
Structure fields are usually public API. You can mark fields as private:
structure S where
x : Nat
private y : Nat
That doesn't affect the current module, but anyone who imports it won't be able to use S.y.
Sometimes it's worth having extra lemmas like that if they restate the field differently.
Dan Abramov (Jul 23 2025 at 22:10):
Ah, cool. Using a Graph structure feels a bit cleaner. With this change, I've gotten rid of the _spec stuff, not sure if that's okay here.
import Mathlib.Tactic
def kpair (a b : Nat) : Set (Set Nat) := {{a}, {a, b}}
lemma kpair_injective : ∀ {a₁ b₁ a₂ b₂}, kpair a₁ b₁ = kpair a₂ b₂ → a₁ = a₂ ∧ b₁ = b₂ := by
intro a1 b1 a2 b2 h
unfold kpair at h
constructor
· contrapose! h
intro hs
rw [Set.ext_iff] at hs
specialize hs {a1}
simp only [Set.mem_insert_iff, Set.mem_singleton_iff, true_or] at hs
rw [← Set.pair_eq_singleton a1, Set.pair_eq_pair_iff] at hs
simp_all
contrapose! h
intro hs
simp only [Set.pair_eq_pair_iff] at hs
rcases hs with (_ | ⟨h1, h2⟩)
· aesop
rw [← Set.pair_eq_singleton a1, Set.pair_eq_pair_iff] at h1
rw [← Set.pair_eq_singleton a2, Set.pair_eq_pair_iff] at h2
simp_all
@[ext]
structure Graph where
pairs : Set (Set (Set Nat))
relation : ∀ p ∈ pairs, ∃ x y, p = kpair x y
defined : ∀ x, ∃ y, kpair x y ∈ pairs
functional : ∀ {x y₁ y₂}, kpair x y₁ ∈ pairs → kpair x y₂ ∈ pairs → y₁ = y₂
def funToGraph (f : Nat → Nat) : Graph where
pairs := {kpair a (f a) | a : Nat}
relation := by
intro p hp
obtain ⟨x, hx⟩ := hp
use x, (f x), hx.symm
defined := by
intro x
use f x
simp
functional := by
intro x y1 y2 h1 h2
obtain ⟨x1, hx1⟩ := h1
obtain ⟨x2, hx2⟩ := h2
obtain ⟨rfl, rfl⟩ := kpair_injective hx1
obtain ⟨rfl, rfl⟩ := kpair_injective hx2
rfl
noncomputable def Graph.toFun (g: Graph) (x : Nat) :=
(g.defined x).choose
lemma Graph.toFun_spec (g: Graph) (x : Nat) : kpair x (g.toFun x) ∈ g.pairs :=
(g.defined x).choose_spec
theorem pairsToFun_funToPairs (f : Nat → Nat) : (funToGraph f).toFun = f := by
set g := funToGraph f
ext x
apply g.functional
· apply g.toFun_spec
simp [g, funToGraph]
theorem funToPairs_surjective (g: Graph) : ∃ f : Nat → Nat, funToGraph f = g := by
set f := g.toFun
use f
ext p
constructor
· rintro ⟨x, rfl⟩
apply g.toFun_spec
intro hp
obtain ⟨x, ⟨_, rfl⟩⟩ := g.relation p hp
use x
have hp' := g.toFun_spec x
rw [g.functional hp hp']
Last updated: Dec 20 2025 at 21:32 UTC