4.5. Rewriting Tactics
The tactic rw applies an equation as a left-to-right rewrite rule, once. It finds the first subterm that matches the left-hand side, instantiates the variables of the equation accordingly, replaces every occurrence of that instantiated subterm, and then tries rfl. A leading ← uses the equation right to left, at h rewrites the hypothesis h instead of the conclusion, and at * rewrites everywhere. Given a constant name instead of an equation, rw uses the defining equations of the constant, which is how rw [Not] expands a negation and rw [add] unfolds our add.
namespace Backward
theorem Eq_trans_symm_rw {α : Type} (a b c : α)
(hab : a = b) (hcb : c = b) : a = c := α:Typea:αb:αc:αhab:a = bhcb:c = b⊢ a = c
α:Typea:αb:αc:αhab:a = bhcb:c = b⊢ b = c
rw [hcb α:Typea:αb:αc:αhab:a = bhcb:c = b⊢ b = b] All goals completed! 🐙
theorem a_proof_of_negation (a : Prop) : a → ¬¬ a := by a:Prop⊢ a → ¬¬a
rw [Not a:Prop⊢ a → ¬a → False] a:Prop⊢ a → ¬a → False
rw [Not a:Prop⊢ a → (a → False) → False] a:Prop⊢ a → (a → False) → False
intro ha hna a:Propha:ahna:a → False⊢ False
apply hna a:Propha:ahna:a → False⊢ a
exact ha All goals completed! 🐙
end Backward
The tactic simp applies a standard set of rewrite rules, the simp set, exhaustively. The syntax simp [t₁, …, tₙ] adds theorems or constants for one invocation, simp [-t] removes one, simp [*] at * uses every hypothesis on every hypothesis and on the conclusion, and the attribute @[simp] registers a theorem permanently.
namespace Backward
theorem cong_two_args_1p1 {α : Type} (a b c d : α)
(g : α → α → ℕ → α) (hab : a = b) (hcd : c = d) :
g a c (1 + 1) = g b d 2 := by α:Typea:αb:αc:αd:αg:α → α → ℕ → αhab:a = bhcd:c = d⊢ g a c (1 + 1) = g b d 2
simp [hab, hcd] All goals completed! 🐙
end Backward
Rewriting is where proofs stop being predictable. The guide's advice is to try a tactic, study the subgoals that emerge, and adjust, rather than to plan every step in advance. In the guide's own words, DON'T PANIC.
4.5.1. Examples
The examples below rewrite in the conclusion and in the hypotheses, in both directions, and compare rw with simp on the same goal.
Example 1. rw [h] rewrites the conclusion left to right and closes it with the rfl it tries at the end.
example (f : ℕ → ℕ) (a b : ℕ) (h : a = b) :
f a = f b := by f:ℕ → ℕa:ℕb:ℕh:a = b⊢ f a = f b
rw [h f:ℕ → ℕa:ℕb:ℕh:a = b⊢ f b = f b] All goals completed! 🐙
Example 2. rw [←h] uses the same equation right to left.
example (f : ℕ → ℕ) (a b : ℕ) (h : a = b) :
f b = f a := by f:ℕ → ℕa:ℕb:ℕh:a = b⊢ f b = f a
rw [←h f:ℕ → ℕa:ℕb:ℕh:a = b⊢ f a = f a] All goals completed! 🐙
Example 3. rw [h₁, h₂] applies two equations in turn.
example (a b c : ℕ) (h₁ : a = b) (h₂ : b = c) :
a = c := by a:ℕb:ℕc:ℕh₁:a = bh₂:b = c⊢ a = c
rw [h₁, a:ℕb:ℕc:ℕh₁:a = bh₂:b = c⊢ b = c h₂ a:ℕb:ℕc:ℕh₁:a = bh₂:b = c⊢ c = c] All goals completed! 🐙
Example 4. rw [h₂] at h₁ rewrites the hypothesis, and the rewritten hypothesis closes the goal.
example (a b c : ℕ) (h₁ : a = b) (h₂ : b = c) :
a = c := by a:ℕb:ℕc:ℕh₁:a = bh₂:b = c⊢ a = c
rw [h₂ a:ℕb:ℕc:ℕh₁:a = ch₂:b = c⊢ a = c] at h₁ a:ℕb:ℕc:ℕh₁:a = ch₂:b = c⊢ a = c
exact h₁ All goals completed! 🐙
Example 5. rw closes the goal on its own once the two sides coincide, because it tries rfl after rewriting.
example (a b : ℕ) (h : a = b) : a = b := by a:ℕb:ℕh:a = b⊢ a = b
rw [h a:ℕb:ℕh:a = b⊢ b = b] All goals completed! 🐙
Example 6. rw [add] unfolds a defining equation of our add.
example (m n : ℕ) :
add m (Nat.succ n) = Nat.succ (add m n) := by m:ℕn:ℕ⊢ add m n.succ = (add m n).succ
rw [add m:ℕn:ℕ⊢ (add m n).succ = (add m n).succ] All goals completed! 🐙
Example 7. rw [Not] at h expands the negation in a hypothesis, which then applies as an implication.
example (a : Prop) (h : ¬a) : a → False := by a:Proph:¬a⊢ a → False
rw [Not a:Proph:a → False⊢ a → False] at h a:Proph:a → False⊢ a → False
exact h All goals completed! 🐙
Example 8. simp alone closes an arithmetic goal from the default simp set.
example (n : ℕ) : n + 0 + 0 = n := by n:ℕ⊢ n + 0 + 0 = n
simp All goals completed! 🐙
Example 9. simp [h] rewrites every occurrence, where rw [h] rewrites only the occurrences of the first matching subterm. The first script needs two rewrites, one per instance of the pattern, and the second needs one simp.
example (f : ℕ → ℕ) (hf : ∀ x, f x = 0) :
f 1 + f 2 = 0 := by f:ℕ → ℕhf:∀ (x : ℕ), f x = 0⊢ f 1 + f 2 = 0
rw [hf f:ℕ → ℕhf:∀ (x : ℕ), f x = 0⊢ 0 + f 2 = 0] f:ℕ → ℕhf:∀ (x : ℕ), f x = 0⊢ 0 + f 2 = 0
rw [hf f:ℕ → ℕhf:∀ (x : ℕ), f x = 0⊢ 0 + 0 = 0] All goals completed! 🐙
example (f : ℕ → ℕ) (hf : ∀ x, f x = 0) :
f 1 + f 2 = 0 := by f:ℕ → ℕhf:∀ (x : ℕ), f x = 0⊢ f 1 + f 2 = 0
simp [hf] All goals completed! 🐙
Example 10. simp [*] at * uses every hypothesis everywhere and closes a goal from two chained hypotheses.
example (a b c : ℕ) (h₁ : a = b) (h₂ : b = c) :
a = c := by a:ℕb:ℕc:ℕh₁:a = bh₂:b = c⊢ a = c
simp [*] at * All goals completed! 🐙