simp — Simplification
simp is Lean's workhorse tactic. It automatically applies a large database of simplification rules to transform your goal. Think of it as an "auto-cleanup" for proofs—it handles the obvious stuff so you can focus on the interesting parts.
The Power of simp
Where rw applies one rule at a time, simpapplies many rules repeatedly until nothing more simplifies. It's like having an assistant that automatically cleans up your proof:
1example (n : Nat) : n + 0 + 0 + 0 = n := by2 simp -- Removes all the + 0s automatically34example (a b c : Nat) : a + b + c + 0 = c + (b + a) := by5 simp [Nat.add_comm, Nat.add_assoc]6 -- Simplifies and uses commutativity/associativity78example : [1, 2] ++ [] ++ [3] = [1, 2, 3] := by9 simp -- Handles list operations1011-- simp can often close goals completely12example (xs : List Nat) : xs ++ [] = xs := by simp13example (n : Nat) : 0 + n = n := by simpsimp repeatedly applies simplification lemmas until the goal can't be simplified further. It often closes goals completely.When to Use simp
Reach for simp when:
- Cleaning up obvious stuff: Arithmetic identities, list operations, boolean simplification
- You don't care about the exact steps: Just want the goal simplified
- Combining many small rewrites: Instead of chaining 5
rws - Working interactively: Try
simpfirst, then refine if needed
What simp Knows
Lemmas marked with @[simp] are automatically used by simp. Lean's standard library has hundreds of these:
1-- These are in the library with @[simp]2#check @Nat.add_zero -- n + 0 = n3#check @Nat.zero_add -- 0 + n = n4#check @Nat.mul_one -- n * 1 = n5#check @Nat.one_mul -- 1 * n = n6#check @List.append_nil -- xs ++ [] = xs7#check @List.nil_append -- [] ++ xs = xs8#check @Bool.not_not -- !!b = b910example (n : Nat) : n + 0 = n := by simp11example (xs : List Nat) : xs ++ [] = xs := by simp12example (b : Bool) : !!b = b := by simpAdding Lemmas to simp
Provide extra lemmas in brackets—your hypotheses or other lemmas you want applied:
1example (a b : Nat) (h : a = 5) : a + b = 5 + b := by2 simp [h] -- Uses h : a = 5 as a simp rule34example (f : Nat → Nat) (hf : ∀ n, f n = n + 1) : f 5 = 6 := by5 simp [hf] -- Applies hf to simplify f 567-- Multiple lemmas8example (a b : Nat) (h1 : a = 0) (h2 : b = 0) : a + b = 0 := by9 simp [h1, h2]1011-- Using with local definitions12def triple (n : Nat) := 3 * n13example : triple 4 = 12 := by simp [triple]simp only — Precise Control
Use simp only to control exactly which lemmas are used. This is preferred in libraries because it's reproducible:
1example (n : Nat) : n + 0 + 0 = n := by2 simp only [Nat.add_zero]3 -- Only uses Nat.add_zero, not the full simp database45-- This is more predictable and often faster6example (a b : Nat) (h : a = b) : a + 0 = b := by7 simp only [Nat.add_zero, h]89-- simp? tells you what simp only would need10example (n : Nat) : n + 0 = n := by11 simp? -- Output: "Try this: simp only [Nat.add_zero]"simp only is preferred in proof libraries because it's reproducible—it won't break if new simp lemmas are added to the database. Use simp? to discover what lemmas you need.Common Mistakes
Mistake 1: Expecting simp to prove everything
1-- simp is powerful but not omniscient2-- example (a b c : Nat) : a * (b + c) = a * b + a * c := by simp 3-- ❌ Fails! Distributivity isn't a simp lemma45-- ✓ Use ring for algebraic identities6example (a b c : Nat) : a * (b + c) = a * b + a * c := by ring78-- ✓ Or provide the lemma explicitly9example (a b c : Nat) : a * (b + c) = a * b + a * c := by10 rw [Nat.mul_add]Mistake 2: Using simp in the middle of proofs
1-- simp can change your goal in unexpected ways2-- This can make it hard to continue the proof34-- ❌ Risky: simp might change things you didn't expect5example (a b c : Nat) (h : a = b) : a + c = b + c := by6 simp -- Might not close the goal!7 -- Now what?89-- ✓ Better: use simp only with specific lemmas10example (a b c : Nat) (h : a = b) : a + c = b + c := by11 simp only [h] -- PredictableMistake 3: Forgetting simp_all when you need hypotheses
1example (a b : Nat) (h1 : a = 5) (h2 : b = 3) : a + b = 8 := by2 simp -- ❌ Might not work—doesn't automatically use h1, h234example (a b : Nat) (h1 : a = 5) (h2 : b = 3) : a + b = 8 := by5 simp_all -- ✓ Uses all hypotheses as simp rulesSimplifying Hypotheses
Like rw, you can simplify hypotheses with at:
1example (n : Nat) (h : n + 0 = 5) : n = 5 := by2 simp at h3 -- h becomes: n = 54 exact h56example (a b : Nat) (h1 : a + 0 = b) (h2 : b + 0 = 5) : a = 5 := by7 simp at *8 -- Simplifies all hypotheses and the goal9 rw [h1, h2]1011-- At specific hypotheses12example (a b : Nat) (h1 : a + 0 = 3) (h2 : b = 4) : a + b = 7 := by13 simp at h114 simp [h1, h2]simp Variants — Complete Guide
Lean provides several variants of simp, each optimized for different situations. Knowing which variant to use can make your proofs cleaner and faster.
simp — The Default
Plain simp uses the entire simp lemma database. It's great for exploration but can be unpredictable as the database grows.
1-- Uses all @[simp] lemmas in scope2example (n : Nat) : n + 0 = n := by simp3example (xs : List Nat) : xs ++ [] = xs := by simp45-- You can add extra lemmas in brackets6example (a : Nat) (h : a = 5) : a + 0 = 5 := by simp [h]simp only — Precise Control
simp only uses ONLY the lemmas you specify—nothing from the database. This is the preferred choice for library code because it's reproducible and won't break when new simp lemmas are added.
1-- Only uses the lemmas you list2example (n : Nat) : n + 0 + 0 = n := by3 simp only [Nat.add_zero]45-- Won't change if simp database changes6example (a b : Nat) (h : a = b) : a + 0 = b := by7 simp only [Nat.add_zero, h]89-- Use simp? to discover what lemmas you need10example (n : Nat) : 0 + n + 0 = n := by11 simp?12 -- Output: Try this: simp only [Nat.zero_add, Nat.add_zero]simp_all — Simplify Everything
simp_all simplifies both the goal AND all hypotheses, treating each hypothesis as a potential simp rule. It's powerful for cleaning up messy contexts.
1-- Simplifies hypotheses AND goal, using all hypotheses as rules2example (a b : Nat) (h1 : a = 5) (h2 : b = 3) : a + b + 0 = 8 := by3 simp_all4 -- h1 and h2 are used as rewrite rules + Nat.add_zero is applied56-- Often closes goals that plain simp can't7example (p q : Prop) (hp : p) (hq : q) : p ∧ q := by simp_all89-- Great for propositional cleanup10example (a b : Nat) (h : a = b) (hb : b = 0) : a = 0 := by simp_allsimp? — Discovery Mode
simp? runs simp and then suggests the minimal simp onlycall that would achieve the same result. Use this to convert exploratory simpcalls into reproducible simp only calls.
1example (n : Nat) : n + 0 = n := by2 simp?3 -- Output: Try this: simp only [Nat.add_zero]4 -- Click the suggestion or copy it!56-- Also works with extra lemmas7example (a : Nat) (h : a = 5) : a + 0 = 5 := by8 simp? [h]9 -- Output: Try this: simp only [Nat.add_zero, h]simp while exploring, then run simp?to get the exact lemmas, and replace with simp only for the final proof.simp_arith — With Arithmetic
simp_arith combines simp with arithmetic decision procedures. It can handle goals involving addition, subtraction, and simple arithmetic.
1-- Handles arithmetic that plain simp doesn't2example (n : Nat) : n + 1 - 1 = n := by simp_arith34example (a b : Nat) : a + b + 1 = 1 + a + b := by simp_arith56-- Combines with regular simp rules7example (n : Nat) : (n + 0) + 1 - 1 = n := by simp_arithdsimp — Definitional Simplification Only
dsimp only applies simplifications that are definitionally equal—no proof obligations, just computation. It's lighter and never fails unexpectedly.
1-- Only unfolds definitions, no rewriting2def myConst := 423example : myConst = 42 := by dsimp [myConst]; rfl45-- Great for unfolding let-bindings6example : (let x := 5; x + x) = 10 := by7 dsimp only -- Unfolds the let8 rfl910-- dsimp never applies rewrite lemmas11example (n : Nat) : n + 0 = n := by12 dsimp -- Does nothing! (Nat.add_zero is a rewrite, not definitional)13 simp -- This workssimp at — Target Specific Locations
Use at to simplify hypotheses instead of (or in addition to) the goal.
1-- Simplify a specific hypothesis2example (n : Nat) (h : n + 0 = 5) : n = 5 := by3 simp at h -- h becomes: n = 54 exact h56-- Simplify multiple hypotheses7example (a b : Nat) (h1 : a + 0 = 3) (h2 : b + 0 = 4) : a + b = 7 := by8 simp at h1 h2 -- Both get simplified9 omega1011-- Simplify everything (all hypotheses + goal)12example (a b : Nat) (h1 : a + 0 = b) (h2 : b + 0 = 5) : a = 5 := by13 simp at * -- Simplifies h1, h2, and goal14 omega1516-- Simplify hypothesis and NOT the goal (use ⊢ for goal)17example (n : Nat) (h : n + 0 = n + 0) : n + 0 = n := by18 simp only [Nat.add_zero] at h ⊢simp [-lemma] — Excluding Lemmas
Sometimes simp applies a lemma you don't want. Use -lemma_nameto exclude specific lemmas from being used.
1-- Prevent simp from using a specific lemma2example (n : Nat) : n + 0 + 0 = n + 0 := by3 simp [-Nat.add_zero] -- Won't simplify n + 0 to n!4 -- Goal is now: n + 0 + 0 = n + 0 (unchanged)5 rfl -- Fails, but demonstrates the point67-- Useful when simp over-simplifies8example (xs : List Nat) : (xs ++ []).length = xs.length := by9 simp [-List.length_append] -- Don't unfold length of append10 simpDefining Your Own Simp Lemmas
1def double (n : Nat) : Nat := n + n23-- Mark as simp lemma with @[simp]4@[simp]5theorem double_zero : double 0 = 0 := rfl67@[simp]8theorem double_succ (n : Nat) : double (n + 1) = double n + 2 := by9 simp [double]10 ring1112-- Now simp uses these automatically13example : double 0 + double 0 = 0 := by simp14example : double 1 = 2 := by simpDeep Dive: How simp Works
simp works by repeatedly applying rewrite rules in a specific order until a fixed point is reached:
- Collect all applicable simp lemmas (from database + provided list)
- Try to apply each lemma to subterms of the goal
- If any lemma matches, apply it and restart from step 2
- Continue until no more lemmas apply (fixed point)
Good simp lemmas always "simplify"—they make terms smaller or more canonical. Bad simp lemmas can cause infinite loops (like bare commutativitya + b = b + a).
1-- ✓ Good simp lemma: always simplifies2@[simp] theorem add_zero (n : Nat) : n + 0 = n := ...34-- ❌ Bad simp lemma: would loop forever5-- @[simp] theorem add_comm : a + b = b + a -- Don't do this!When simp Doesn't Work
If simp doesn't close your goal, try these alternatives:
1-- For arithmetic/algebra: use ring, omega, or linarith2example (a b c : Nat) : a + (b + c) = (a + b) + c := by3 ring -- or: rw [Nat.add_assoc]45example (n : Nat) (h : n > 5) : n > 3 := by6 omega78-- For specific rewrites: use rw9example (a b : Nat) (h : a = b) : a + 1 = b + 1 := by10 rw [h]1112-- For case analysis: use cases or decide13example (b : Bool) : b || true = true := by14 cases b <;> simpBest Practices
- Try simp first when exploring—it often works
- Use simp? to discover what lemmas you need, then convert to
simp only - In libraries, prefer simp only for reproducibility
- Use simp_all when you want hypotheses used automatically
- Don't fight simp—if it's not working, try a different tactic
Use simp to close this goal.
1example (xs : List Nat) : [] ++ xs ++ [] = xs := by2 simpUse simp with a hypothesis to close the goal.
1example (n : Nat) (h : n = 3) : n + 0 = 3 := by2 simp [h]Use simp? to find out what lemmas you need.
1example (a b : Nat) : a + 0 + b + 0 = a + b := by2 simp? -- Reveals: simp only [Nat.add_zero]3 -- Then use:4 simp only [Nat.add_zero]