Zulip Chat Archive
Stream: new members
Topic: MIL Ch2.1 exercise
Jeremy Aube (Feb 21 2026 at 21:35):
I was going through the exercises in MIL Ch2.1 and got to this exercise:
The following exercise is a little more challenging. You can use the theorems listed underneath.
example (a b : ℝ) : (a + b) * (a - b) = a ^ 2 - b ^ 2 := by
sorry
#check pow_two a
#check mul_sub a b c
#check add_mul a b c
#check add_sub a b c
#check sub_sub a b c
#check add_zero a
I was able to solve with this, but only by using sub_add_cancel, which isn't mentioned until later:
example (a b : ℝ) : (a + b) * (a - b) = a ^ 2 - b ^ 2 := by
rw [add_mul, mul_sub, ← pow_two]
rw [mul_sub, ← pow_two, add_sub]
rw [mul_comm, sub_add_cancel]
Is there some way to solve this that only uses theorems mentioned up to that point in the guide?
Daniel Horton (Feb 22 2026 at 01:00):
I've recently picked up Lean and have been going through the MIL exercises as well. Here's what I wrote for that exercise:
example (a b : ℝ) : (a + b) * (a - b) = a ^ 2 - b ^ 2 := by
calc
(a + b) * (a - b) = a ^ 2 - a * b + a * b - b ^ 2 := by
rw [add_mul, mul_sub, mul_sub, ← pow_two, ← pow_two, mul_comm b, add_sub]
_ = a ^ 2 - b ^ 2 := by
rw [add_comm, add_sub, add_comm, ← add_sub, sub_self, add_zero]
It looks like I avoided using sub_add_cancel by instead using the theorem sub_self which was introduced earlier in the chapter.
Jeremy Aube (Feb 23 2026 at 02:12):
@Daniel Horton thanks! That was it, I was missing that middle
rw [add_comm, add_sub, add_comm]
to rotate the terms. That’s a neat trick.
Michael (Feb 23 2026 at 20:24):
It feels worth mentioning conv here - I don't think MIL mentions it, but it's really convenient for applying rewrites to complex statements.
https://lean-lang.org/doc/reference/latest/Tactic-Proofs/Targeted-Rewriting-with--conv/
Jeremy Aube (Feb 23 2026 at 23:28):
Interesting, that seems similar to nth_rw, but I don’t know enough to know what the difference is.
Michael (Feb 23 2026 at 23:50):
Try stepping through this:
import Mathlib.Algebra.BigOperators.Fin
import Mathlib.Algebra.Group.Basic
import Mathlib.Data.Set.Basic
section
variable (G : Type*) [CommGroup G] [Finite G]
example {_ : Fintype G} {x : G} (hx : x = ∏ g : G, g) : x * x = 1 := by
let G' := {g | g : G}
have _ := Fintype.ofFinite G' -- we need this to use ∏
have hx' : x = ∏ g ∈ G', g := by
rw [hx]
apply Finset.prod_bijective id Function.bijective_id
· intro i
simp only [Finset.mem_univ, id_eq, Set.mem_toFinset, true_iff]
use i
· intro g hg; unfold id; rfl
have hxinv' : x = ∏ g ∈ G', g⁻¹ := by
rw [hx']
apply Finset.prod_bijective (·⁻¹)
· apply And.intro inv_injective inv_surjective
· intro g
simp only [Set.mem_toFinset]
apply Iff.intro
· intro hg; use g⁻¹
· intro hg; use g
· intro g hg; rw [inv_inv]
-- do some rewriting to match the assumptions of Finset.prod_mul_prod_comm
conv => lhs; lhs; rw [hx']; arg 2; ext g; rw [← mul_one g, ← id_eq g]
conv => lhs; rhs; rw [hxinv']; arg 2; ext g; rw [← mul_one g⁻¹]
rw [Finset.prod_mul_prod_comm id (fun a ↦ (1 : G)) (·⁻¹) (fun a ↦ (1 : G))]
-- for manual solving, the relevant theorem is Finset.prod_eq_one
-- rw [id_eq] -- this fails, but conv can do it:
conv => lhs; rw [one_mul]; lhs; arg 2; ext g; rw [id_eq, mul_inv_cancel]
simp
end
conv seems to be able to navigate into structures that rw can't.
In your case, you can do rewrites by carefully specifying exactly where you want them done, but it feels more intuitive to me doing it with conv.
(I don't know why the rw [id_eq] fails -- rw [one_mul] doesn't, but the context looks identical to me. And unfold id will succeed in the same context where rw [id_eq] doesn't.)
Aaron Liu (Feb 24 2026 at 00:23):
Michael said:
Try stepping through this:
import Mathlib.Algebra.BigOperators.Fin import Mathlib.Algebra.Group.Basic import Mathlib.Data.Set.Basic section variable (G : Type*) [CommGroup G] [Finite G] example {_ : Fintype G} {x : G} (hx : x = ∏ g : G, g) : x * x = 1 := by let G' := {g | g : G} have _ := Fintype.ofFinite G' -- we need this to use ∏ have hx' : x = ∏ g ∈ G', g := by rw [hx] apply Finset.prod_bijective id Function.bijective_id · intro i simp only [Finset.mem_univ, id_eq, Set.mem_toFinset, true_iff] use i · intro g hg; unfold id; rfl have hxinv' : x = ∏ g ∈ G', g⁻¹ := by rw [hx'] apply Finset.prod_bijective (·⁻¹) · apply And.intro inv_injective inv_surjective · intro g simp only [Set.mem_toFinset] apply Iff.intro · intro hg; use g⁻¹ · intro hg; use g · intro g hg; rw [inv_inv] -- do some rewriting to match the assumptions of Finset.prod_mul_prod_comm conv => lhs; lhs; rw [hx']; arg 2; ext g; rw [← mul_one g, ← id_eq g] conv => lhs; rhs; rw [hxinv']; arg 2; ext g; rw [← mul_one g⁻¹] rw [Finset.prod_mul_prod_comm id (fun a ↦ (1 : G)) (·⁻¹) (fun a ↦ (1 : G))] -- for manual solving, the relevant theorem is Finset.prod_eq_one -- rw [id_eq] -- this fails, but conv can do it: conv => lhs; rw [one_mul]; lhs; arg 2; ext g; rw [id_eq, mul_inv_cancel] simp end
Note that ∏ g : G, g is short for ∏ g ∈ Finset.univ, g, so you can directly apply lemmas about ∏ x ∈ s, f x and G' is unnecessary.
Aaron Liu (Feb 24 2026 at 00:26):
Michael said:
(I don't know why the
rw [id_eq]fails --rw [one_mul]doesn't, but the context looks identical to me. Andunfold idwill succeed in the same context whererw [id_eq]doesn't.)
rw can't match bound variables. So since 1 * 1 doesn't contain any bound variables, rw [one_mul] will work, but id a has the bound variable a so rw [id_eq] fails.
Jeremy Aube (Feb 24 2026 at 01:54):
That ext is interesting. Is that something that's only available to conv? Is that what's removing the bound variable so rw [id_eq] can succeed?
Michael (Feb 24 2026 at 02:31):
Aaron Liu said:
Note that
∏ g : G, gis short for∏ g ∈ Finset.univ, g, so you can directly apply lemmas about∏ x ∈ s, f xandG'is unnecessary.
Thanks! Something that bothered me about the proof is that I needed to assume {_ : Fintype G} to make the definition of hx typecheck, even though G is declared with [Finite G]. I don't like it because that Fintype assumption is independent of the [Finite G] - if I comment out [Finite G], the example still compiles just fine. Is there a better way to pose the example?
Michael (Feb 24 2026 at 02:35):
Jeremy Aube said:
That
extis interesting. Is that something that's only available toconv? Is that what's removing the bound variable sorw [id_eq]can succeed?
Yes, the ext navigates into the binding, and to my understanding the rw then runs in a context where the bound variable is just one of your facts.
ext x traverses into a binder (a fun x => e or ∀ x, e expression) to target e, introducing name x in the process.
Last updated: Feb 28 2026 at 14:05 UTC