Custom Tactics
Build your own tactics to automate repetitive proof patterns. Lean's metaprogramming system lets you extend the tactic language.
Why Custom Tactics?
When you find yourself writing the same proof patterns repeatedly, you can create a custom tactic that encapsulates that pattern.
lean
1-- Instead of writing this pattern repeatedly:2example (a b c : Nat) (h1 : a = b) (h2 : b = c) : a = c := by3 rw [h1, h2]45-- Create a tactic that handles it automaticallySimple Tactic Macros
lean
1-- The simplest way: macro syntax2macro "my_trivial" : tactic => `(tactic| trivial)34example : True := by my_trivial56-- With parameters7macro "my_simp" n:term : tactic => `(tactic| simp [$n])89example (h : a = b) : a = b := by my_simp hKey Takeaway
macro creates simple tactic abbreviations. For complex logic, use elab or theTactic monad.Combining Tactics
lean
1-- Macro for a common pattern2macro "finish" : tactic => `(tactic| 3 first4 | rfl5 | simp6 | trivial7 | assumption8)910example : 1 + 1 = 2 := by finish11example (h : P) : P := by finishThe TacticM Monad
lean
1import Lean2open Lean Elab Tactic34-- For complex tactics, use TacticM5elab "my_intro" : tactic => do6 let goal ← getMainGoal7 let (_, newGoal) ← goal.intro `h8 replaceMainGoal [newGoal]910-- Now my_intro works like intro11example : P → P := by12 my_intro13 assumptionℹ
Custom tactics using
TacticM have full access to the proof state and can perform complex manipulations.Reading the Goal
lean
1import Lean2open Lean Elab Tactic Meta34-- A tactic that prints goal information5elab "show_goal" : tactic => do6 let goal ← getMainGoal7 let goalType ← goal.getType8 logInfo m!"Goal: {goalType}"910example : 1 + 1 = 2 := by11 show_goal -- Prints: Goal: 1 + 1 = 212 rflWorking with Hypotheses
lean
1import Lean2open Lean Elab Tactic Meta34-- Find and use a hypothesis of the right type5elab "exact_hyp" : tactic => do6 let goal ← getMainGoal7 let goalType ← goal.getType8 let lctx ← getLCtx9 for ldecl in lctx do10 if ldecl.isImplementationDetail then continue11 let declType := ldecl.type12 if ← isDefEq declType goalType then13 goal.assign ldecl.toExpr14 return15 throwError "No hypothesis matches the goal"Deep Dive: Metaprogramming Architecture
Lean's metaprogramming is based on several monads:
CoreM— Basic environment accessMetaM— Type checking, unificationTermElabM— Term elaborationTacticM— Tactic execution
Each builds on the previous, adding capabilities. Tactics typically run in TacticM.
Practical Example: repeat_on
lean
1import Lean2open Lean Elab Tactic34-- Apply a tactic to all goals5syntax "all_goals" tactic : tactic67macro_rules8 | `(tactic| all_goals $t) => `(tactic| focus (repeat ($t)))910-- Example usage11example : True ∧ True ∧ True := by12 constructor13 constructor14 all_goals trivialError Handling
lean
1import Lean2open Lean Elab Tactic34-- Tactics can fail gracefully5elab "try_rfl" : tactic => do6 try7 evalTactic (← `(tactic| rfl))8 catch _ =>9 logInfo "rfl failed, trying simp"10 evalTactic (← `(tactic| simp))1112example : 1 + 1 = 2 := by try_rfl13example (h : a = b) : a = b := by try_rflBest Practices
lean
1-- 1. Start with macros for simple patterns2macro "intro_and_split" : tactic => 3 `(tactic| intro h; constructor)45-- 2. Use elab for logic that needs goal inspection6elab "smart_close" : tactic => do7 first8 | evalTactic (← `(tactic| rfl))9 | evalTactic (← `(tactic| assumption))10 | throwError "smart_close failed"1112-- 3. Document your tactics13/-- Applies either rfl or assumption to close the goal. -/14elab "close" : tactic => ...