Zulip Chat Archive
Stream: lean4
Topic: General strategy for theorems about complicated definitions
Jannis (Feb 17 2026 at 13:45):
Hi!
So I'm writing some very basic graph theory proofs, but my definitions are getting a bit tangled to the point that I have relatively complicated definitions e.g. for Path.append:
def Path.append
(p1 : Path graph) (p2 : Path graph)
(hp1 : p1.IsBetween u v) (hp2 : p2.IsBetween v w)
(hvOnlyCommon : OnlyCommon v p1 p2) :
{ p : Path graph // p.IsBetween u w ∧ p.length = p1.length + p2.length - 1 } := by ...
As you can see, I modeled some properties of the return value of Path.append through the use of a subtype.
I think modeling Path.IsBetween u w with the subtype is fine, but I am not happy with having to model the length properties in there too.
This leads me to my first question: It's surely not idiomatic to just prove everything about a definition inside the definition itself and then have the proven theorems be properties of the subtype like I did, right?
Let me now explain, why I even chose to put the length properties in the subtype.
This is because my Path.append definition has to do a bit of work to actually append the paths (orient the given paths, which are basically lists of vertices).
I tried proving the length properties from an external theorem, but I couldn't get that to work, because unfolding Path.append (and Path.length) lead to something I couldn't really work with:
⊢ (↑(match ⟨op1, hop1⟩ with
| ⟨op1, ⋯⟩ =>
match p2.orient hp2 with
| ⟨op2, ⋯⟩ =>
⟨{
walk :=
{ vertices := op1.walk.vertices ++ op2.walk.vertices.tail, isNonempty := ⋯,
hasAdjacentVertices := ⋯ },
isNonRepeating := ⋯ },
⋯⟩)).walk.vertices.length =
p1.walk.vertices.length + p2.walk.vertices.length - 1
This leads me to my second question: Is there some way of simplifying this goal state? That would enable me to actually prove properties such as the length properties in external theorems, which I think would be more idiomatic as the properties of definitions like Path.append would then be extensible from the outside.
Jannis (Feb 17 2026 at 14:05):
So I actually managed to prove the length properties from an external theorem now:
theorem path_append_length
(p1 : Path graph) (p2 : Path graph)
(hp1 : p1.IsBetween u v) (hp2 : p2.IsBetween v w)
(hvOnlyCommon : OnlyCommon v p1 p2) :
(let p := (p1.append p2 hp1 hp2 hvOnlyCommon).val
p.length = p1.length + p2.length - 1) := by
simp [Path.append, Path.length]
obtain ⟨_, _, _, _⟩ := p1.orient hp1
obtain ⟨_, _, _, _⟩ := p2.orient hp2
have : p2.walk.vertices.length > 0 := List.length_pos_of_ne_nil p2.walk.isNonempty
grind
This now leaves only my first question to be answered.
Max Nowak 🐉 (Feb 17 2026 at 16:26):
It generally makes more sense to first implement the function itself, and then prove properties about it. When you define addition on numbers, you don't have a type add : Nat -> Nat -> { res : Nat // is commutative and all the other properties } either.
Max Nowak 🐉 (Feb 17 2026 at 16:39):
If you often handle objects like "path from A to B", then you can still define a helper after defining append and after proving properties about it. Something like def PathBetween (a b : Vertex) : Type := { p : Path // ... }, and then define some convenience combinators, like def PathBetween.append (p1 : PathBetween A B) (p2 : PathBetween B C) : PathBetween A C := ...
Last updated: Feb 28 2026 at 14:05 UTC