Tactic Combinators
Tactic combinators let you compose tactics, repeat them, apply them conditionally, and handle multiple goals elegantly.
Sequencing: semicolon
Use ; to chain tactics, where the second applies to all goals created by the first:
lean
1-- ; applies the next tactic to ALL goals2example (P Q : Prop) (hp : P) (hq : Q) : P ∧ Q := by3 constructor <;> assumption4 -- constructor creates 2 goals5 -- assumption is applied to both67-- Same as:8example (P Q : Prop) (hp : P) (hq : Q) : P ∧ Q := by9 constructor10 · assumption11 · assumption1213-- Chain multiple14example (a b : Nat) : a ≤ a + b ∧ b ≤ a + b := by15 constructor <;> omegaKey Takeaway
tac1 <;> tac2 runs tac1, then runs tac2 onevery goal created by tac1. Great for repetitive subgoals.first: Try Alternatives
lean
1-- first tries tactics until one succeeds2example (n : Nat) : n = n := by3 first4 | omega5 | rfl6 | trivial78-- Useful when the right tactic depends on the goal9example (a b : Nat) (h : a = b) : a = b ∨ a > b := by10 first11 | right; omega -- fails12 | left; exact h -- succeedsrepeat: Apply Until It Fails
lean
1-- repeat keeps applying until the tactic fails2example : [1, 2, 3].length = 3 := by3 repeat (first | rfl | simp [List.length])45-- Useful for intro6example : ∀ a b c : Nat, a + b + c = c + b + a := by7 repeat intro8 ring910-- repeat can loop forever if the tactic always succeeds!11-- Be careful with repeat simp (usually fine)try: Optional Tactic
lean
1-- try runs a tactic but doesn't fail if it fails2example (a b : Nat) : a + b = b + a := by3 try rfl -- fails silently (a + b ≠ b + a by definition)4 ring56-- Useful for cleanup that might not apply7example (n : Nat) : n + 0 = n := by8 try simp -- succeeds9 try ring -- already done, but try doesn't failall_goals
lean
1-- all_goals applies a tactic to all current goals2example (a b c : Nat) : a = a ∧ b = b ∧ c = c := by3 constructor4 all_goals rfl56-- Different from ; which chains7example (P Q R : Prop) (hp : P) (hq : Q) (hr : R) : P ∧ Q ∧ R := by8 refine ⟨?_, ?_, ?_⟩9 all_goals assumptionany_goals
lean
1-- any_goals succeeds if any goal is solved2example (a b : Nat) (ha : a = 1) (hb : b = 2) : a = 1 ∧ b = 2 := by3 constructor4 any_goals exact ha5 exact hb67-- Useful when only some goals can be handled8example (n : Nat) : (n = 0 ∨ n > 0) ∧ n ≥ 0 := by9 constructor10 · omega11 · omegaDeep Dive: Combinator Semantics
Understanding how combinators interact with goals:
lean
1-- Goals are a list; tactics operate on them23-- ; : run tac1, then tac2 on each resulting goal4-- all_goals : run tac on every goal in the list5-- any_goals : run tac on each goal, succeed if any succeeds6-- first : try each tactic on current goal until success7-- repeat : keep running tac until it fails8-- try : run tac, always succeed (even if tac fails)910-- Nesting:11-- all_goals (first | tac1 | tac2)12-- Tries tac1 then tac2 on each goalfocus
lean
1-- focus restricts to the first goal2example (P Q : Prop) (hp : P) (hq : Q) : P ∧ Q := by3 constructor4 focus5 -- Only works on first goal (P)6 exact hp7 -- Back to second goal (Q)8 exact hq910-- Rarely needed in practice; · does similarrotate_left / rotate_right
lean
1-- Reorder goals2example (P Q R : Prop) (hp : P) (hq : Q) (hr : R) : P ∧ Q ∧ R := by3 refine ⟨?a, ?b, ?c⟩4 rotate_left 2 -- Move first 2 goals to the end5 -- Now goals are: R, P, Q6 · exact hr7 · exact hp8 · exact hqℹ
Named goals with
?name let you address them out of order using case name =>.Practical Patterns
lean
1-- Pattern 1: Split and solve uniformly2example (a b c d : Nat) : a ≤ a + b ∧ b ≤ b + c ∧ c ≤ c + d := by3 refine ⟨?_, ?_, ?_⟩ <;> omega45-- Pattern 2: Try multiple approaches6example (n : Nat) (h : n > 0 ∨ n = 0) : n ≥ 0 := by7 cases h with8 | inl h => omega9 | inr h => omega1011-- Pattern 3: Clean up trivial goals12example (P : Prop) (hp : P) : P ∧ True ∧ P := by13 refine ⟨?_, ?_, ?_⟩14 · exact hp15 · trivial16 · exact hpdone
lean
1-- done asserts there are no remaining goals2example (n : Nat) : n = n := by3 rfl4 done -- Confirms proof is complete56-- Useful for sanity checking in complex proofs7example (P : Prop) (hp : P) : P ∧ P := by8 constructor <;> exact hp9 doneExamples
Induction with Uniform Cases
lean
1example (n : Nat) : n + 0 = n ∧ 0 + n = n := by2 induction n with3 | zero => constructor <;> rfl4 | succ m ih => 5 obtain ⟨ih1, ih2⟩ := ih6 constructor7 · simp [ih1]8 · simp [ih2]Multiple Rewrites
lean
1example (a b c : Nat) (h1 : a = b) (h2 : b = c) : 2 a + 1 = c + 1 ∧ a + 2 = c + 2 := by3 constructor <;> omegaTry Pattern
lean
1-- Normalize then attempt automation2example (a b : Nat) (h : a = b) : (a + 1) * (a + 1) = (b + 1) * (b + 1) := by3 try ring_nf4 rw [h]