assumption tries to solve the main goal using a hypothesis of compatible type, or else fails.
Note also the ‹t› term notation, which is a shorthand for showtbyassumption.
Closing a Goal from Context
Here the hypothesis h₃ has exactly the type needed by the goal, so assumption finds it automatically:
apply_assumption looks for an assumption of the form ...→∀_,...→head
where head matches the current goal.
You can specify additional rules to apply using apply_assumption[...].
By default apply_assumption will also try rfl, trivial, congrFun, and congrArg.
If you don't want these, or don't want to use all hypotheses, use apply_assumptiononly[...].
You can use apply_assumption[-h] to omit a local hypothesis.
You can use apply_assumptionusing[a₁,...] to use all lemmas which have been labelled
with the attributes aᵢ (these attributes must be created using register_label_attr).
If apply_assumption fails, it will call exfalso and try again.
Thus if there is an assumption of the form P→¬Q, the new tactic state
will have two goals, P and Q.
You can pass a further configuration via the syntax apply_rules(config:={...})lemmas.
The options supported are the same as for solve_by_elim (and include all the options for apply).
The intro tactic makes progress on goals whose target type is an implication, universal quantifier, or function type.
It introduces the hypothesis into the local context as a new assumption (for propositions) or a new local variable (for data) and changes the goal to the function's body.
Introduces one or more hypotheses, optionally naming and/or pattern-matching them.
For each hypothesis to be introduced, the remaining main goal's target type must
be a let or function type.
intro by itself introduces one anonymous hypothesis, which can be accessed
by e.g. assumption. It is equivalent to intro_.
introxy introduces two hypotheses and names them. Individual hypotheses
can be anonymized via _, given a type ascription, or matched against a pattern:
intro(a,b)-- ..., a : α, b : β ⊢ ...
introrfl is short for introh;substh, if h is an equality where the left-hand or right-hand side
is a variable.
Alternatively, intro can be combined with pattern matching much like fun:
intro
| n + 1, 0 => tac
| ...
Introducing an Implication
The goal P → R is an implication. intro moves its premise into the context as hp:
Multiple names can be provided to introduce several assumptions at once.
Calling intro once with multiple names is equivalent to calling it multiple times:
intros repeatedly applies intro to introduce zero or more hypotheses
until the goal is no longer a binding expression
(i.e., a universal quantifier, function type, implication, or have/let),
without performing any definitional reductions (no unfolding, beta, eta, etc.).
The introduced hypotheses receive inaccessible (hygienic) names.
introsxyz is equivalent to introxyz and exists only for historical reasons.
The intro tactic should be preferred in this case.
Properties and relations
intros succeeds even when it introduces no hypotheses.
repeatintro is like intros, but it performs definitional reductions
to expose binders, and as such it may introduce more hypotheses than intros.
intros is equivalent to intro _ _ … _,
with the fewest trailing _ placeholders needed so that the goal is no longer a binding expression.
The trailing introductions do not perform any definitional reductions.
Examples
Implications:
example(pq:Prop):p→q→p:=p:Propq:Prop⊢ p→q→pp:Propq:Propa✝¹:pa✝:q⊢ p/- Tactic state
a✝¹ : p
a✝ : q
⊢ p -/All goals completed! 🐙
The rintro tactic is a combination of the intros tactic with rcases to
allow for destructuring patterns while introducing variables. See rcases for
a description of supported patterns. For example, rintro(a|⟨b,c⟩)⟨d,e⟩
will introduce two variables, and then do case splits on both of them producing
two subgoals, one with variables ade and the other with bcde.
rintro, unlike rcases, also supports the form (xy:ty) for introducing
and type-ascripting multiple variables at once, similar to binders.
Introducing and Destructuring
The anonymous constructor pattern ⟨hp, hq⟩ destructs the conjunction as it is introduced:
When matching on an equality where one side is a single variable also matched by rintro, one
can use rfl as a pattern name. This causes the equality to be immediately used as a substition applied to the goal.
Here, instead of introducing a hypothesis h : 7 * b = a, rintro directly replaces a with 7 * b:
The rfl tactic succeeds on instances of reflexive relations.
A common case is when the goal is an equality of two terms are definitionally equal.
Other reflexive relations can also be tagged with attributes and used with rfl, for which see below.
This tactic applies to a goal whose target has the form x~x,
where ~ is equality, heterogeneous equality or any relation that
has a reflexivity lemma tagged with the attribute @[refl].
Reflexivity
When both sides of an equation are the same, rfl closes the goal immediately:
The rfl tactic works with any relation that has a lemma tagged with the refl attribute, not just equality.
For instance, it can close goals involving Iff:
rfl' is similar to rfl, but disables smart unfolding and unfolds all kinds of definitions,
theorems included (relevant for declarations defined by well-founded recursion).
The same as rfl, but without trying eq_refl at the end.
attributeReflexive Relations
The refl attribute marks a lemma as a proof of reflexivity for some relation.
These lemmas are used by the rfl, rfl', and apply_rfl tactics.
attr::= ...
|refl
The symm tactic swaps the two sides of a symmetric relation in the goal, such as turning a = b into b = a.
It can also be applied to a hypothesis with symm at h.
symm applies to a goal whose target has the form t~u where ~ is a symmetric relation,
that is, a relation which has a symmetry lemma tagged with the attribute [symm].
It replaces the target with u~t.
For every hypothesis h:a~b where a @[symm] lemma is available,
add a hypothesis h_symm:b~a.
attributeSymmetric Relations
The symm attribute marks a lemma as a proof that a relation is symmetric.
These lemmas are used by the symm and symm_saturate tactics.
attr::= ...
|symm
The calc tactic opens a calculation block for chaining a sequence of relation steps (equalities, inequalities, etc.), where each step is justified by a tactic.
proves a=z from the given step-wise proofs. = can be replaced with any
relation implementing the typeclass Trans. Instead of repeating the right-
hand sides, subsequent left-hand sides can be replaced with _.
calc
a = b := pab
_ = c := pbc
...
_ = z := pyz
It is also possible to write the first relation as <lhs>\n_=<rhs>:=<proof>. This is useful for aligning relation symbols, especially on longer
identifiers:
The subst tactic eliminates a variable that is known to be equal to some expression.
Given a hypothesis h : x = e or h : e = x where x is a local variable, it replaces all occurrences of x with e throughout the goal and context, and removes both x and h.
subst_eq repeatedly substitutes according to the equality proof hypotheses in the context,
replacing the left side of the equality with the right, until no more progress can be made.
Apply congruence (recursively) to goals of the form ⊢fas=fbs and ⊢fas≍fbs.
The optional parameter is the depth of the recursive applications.
This is useful when congr is too aggressive in breaking down the goal.
For example, given ⊢f(g(x+y))=f(g(y+x)),
congr produces the goals ⊢x=y and ⊢y=x,
while congr2 produces the intended ⊢x+y=y+x.
Basic Congruence
The goal n + 1 = m + 1 is reduced to n = m, which congr closes using the hypothesis h:
A numeric argument controls how many layers congr descends.
Here congr 2 peels off two layers of arithmetic operations, leaving a * c = b * c as a subgoal that still needs to be solved:
Using uncontrolled congr would have left us with the goal a = b.
We cannot prove this, because even though b * c = a * c, it might be because c is zero.
ac_nf normalizes equalities up to application of an associative and commutative operator.
ac_nf normalizes all hypotheses and the goal target of the goal.
ac_nfatl normalizes at location(s) l, where l is either * or a
list of hypotheses in the local context. In the latter case, a turnstile ⊢ or |-
can also be used, to signify the target of the goal.
The exact tactic proves the current goal by providing a term whose type matches the goal's target type.
It works up to definitional equality, so the term's type does not need to be syntactically identical to the goal.
The apply tactic works backwards from the goal.
Given an expression whose type is a function type ending in the goal's target, it replaces the goal with one subgoal for each remaining argument.
Where exact requires the term to have exactly the goal's type, apply allows the term to require additional premises that become new goals.
applye tries to match the current goal against the conclusion of e's type.
If it succeeds, then the tactic returns as many subgoals as the number of premises that
have not been fixed by type inference or type class resolution.
Non-dependent premises are added before dependent ones.
The apply tactic uses higher-order pattern matching, type class resolution,
and first-order unification with dependent types.
Reducing a Goal with an Implication
Applying hpq reduces the goal from Q to P, which can then be closed with exact:
Note that apply does not work directly with ↔ (if-and-only-if) hypotheses.
To use a hypothesis h : P ↔ Q backwards on the goal, use rw instead, or extract one direction with h.mp or h.mpr.
The refine tactic is like exact, but allows holes written as ?_ that become new goals.
This is useful when part of a term is known but some arguments still need to be proved.
It is also often useful for decomposing goals with anonymous constructor syntax.
refinee behaves like exacte, except that named (?x) or unnamed (?_)
holes in e that are not solved by unification with the main goal's target type
are converted into new goals, using the hole's name, if any, as the goal case name.
Exact with Holes
The anonymous constructor provides the witness 2, while ?_ leaves the proof obligation 2 + 2 = 4 as a new goal:
solve_by_elim calls apply on the main goal to find an assumption whose head matches
and then repeatedly calls apply on the generated subgoals until no subgoals remain,
performing at most maxDepth (defaults to 6) recursive steps.
solve_by_elim performs backtracking if subgoals can not be solved.
By default, the assumptions passed to apply are the local context, rfl, trivial,
congrFun and congrArg.
The assumptions can be modified with similar syntax as for simp:
solve_by_elim[h₁,h₂,...,hᵣ] also applies the given expressions.
solve_by_elimonly[h₁,h₂,...,hᵣ] does not include the local context,
rfl, trivial, congrFun, or congrArg unless they are explicitly included.
solve_by_elim[-h₁,...-hₙ] removes the given local hypotheses.
solve_by_elimusing[a₁,...] uses all lemmas which have been labelled
with the attributes aᵢ (these attributes must be created using register_label_attr).
solve_by_elim* tries to solve all goals together, using backtracking if a solution for one goal
makes other goals impossible.
(Adding or removing local hypotheses may not be well-behaved when starting with multiple goals.)
Optional arguments passed via a configuration argument as solve_by_elim(config:={...})
maxDepth: number of attempts at discharging generated subgoals
symm: adds all hypotheses derived by symm (defaults to true).
transparency: change the transparency mode when calling apply. Defaults to .default,
but it is often useful to change to .reducible,
so semireducible definitions will not be unfolded when trying to apply a lemma.
See also the doc-comment for Lean.Meta.Tactic.Backtrack.BacktrackConfig for the options
proc, suspend, and discharge which allow further customization of solve_by_elim.
Both apply_assumption and apply_rules are implemented via these hooks.
apply_rules[l₁,l₂,...] tries to solve the main goal by iteratively
applying the list of lemmas [l₁,l₂,...] or by applying a local hypothesis.
If apply generates new goals, apply_rules iteratively tries to solve those goals.
You can use apply_rules[-h] to omit a local hypothesis.
apply_rules will also use rfl, trivial, congrFun and congrArg.
These can be disabled, as can local hypotheses, by using apply_rulesonly[...].
You can use apply_rulesusing[a₁,...] to use all lemmas which have been labelled
with the attributes aᵢ (these attributes must be created using register_label_attr).
You can pass a further configuration via the syntax apply_rules(config:={...}).
The options supported are the same as for solve_by_elim (and include all the options for apply).
apply_rules will try calling symm on hypotheses and exfalso on the goal as needed.
This can be disabled with apply_rules(config:={symm:=false,exfalso:=false}).
You can bound the iteration depth using the syntax apply_rules(config:={maxDepth:=n}).
Unlike solve_by_elim, apply_rules does not perform backtracking, and greedily applies
a lemma from the list until it gets stuck.
as_aux_lemma=>tac does the same as tac, except that it wraps the resulting expression
into an auxiliary lemma. In some cases, this significantly reduces the size of expressions
because the proof term is not duplicated.
The exfalso tactic changes the goal to False. It is named after the Latin phrase ex falso quodlibet, that is, “from falsehood, anything follows”.
This is useful when the hypotheses are contradictory: once the goal is False, it can be closed by deriving a contradiction.
Because it discards the original goal entirely, exfalso should only be used when the hypotheses are genuinely contradictory.
If they are not, the resulting False goal will be unsolvable.
The hypothesis h : n < n is contradictory because no number is strictly less than itself.
After exfalso changes the goal to False, we can close it using the irreflexivity lemma:
The contradiction tactic automatically closes a goal when the hypotheses are trivially contradictory, without requiring the user to identify the specific contradiction.
If the goal is an implication or a function type, introduce the argument and restart.
(In particular, if the goal is x≠y, introduce x=y.)
Otherwise, for a propositional goal P, replace it with ¬¬P
(attempting to find a Decidable instance, but otherwise falling back to working classically)
and introduce ¬P.
We first show that it suffices to know that the list of the length is 3 in order to conclude that
the list has nonzero length. Then we prove that the list does in fact have lengtht three.
The change tactic replaces the goal (or a hypothesis) with a definitionally equal alternative.
This can make the goal easier to read or bring it into a form that other tactics expect.
generalize([h:]e=x),+ replaces all occurrences es in the main goal
with a fresh hypothesis xs. If h is given, h:e=x is introduced as well.
generalizee=xath₁...hₙ also generalizes occurrences of e
inside h₁, ..., hₙ.
generalizee=xat* will generalize occurrences of e everywhere.
The specialize tactic instantiates a universally quantified or function-typed hypothesis with specific arguments, replacing it in the context with the result.
Because specialize modifies the hypothesis in place, the original general statement is lost after specialization.
If the original hypothesis is needed again, use have to create a copy first, for example have h' := h before specializing h or h'.
specializeha₁...aₙ is equivalent to replaceh:=ha₁...aₙ.
It specializes the local hypothesis h by instantiating
universal quantifications and implications using the concrete terms a₁ ... aₙ.
The tactic adds a new hypothesis with the same name and tries to remove
the original h if possible.
Example: given h:∀(n:Nat),pn→qn and h':p2,
then specializeh2h' replaces h with h:q2.
The tactic also supports instantiating particular universal quantifiers
using named argument syntax. Example: given h:∀(mn:Nat),pmn,
then specializeh(n:=2) replaces h with h:∀(m:Nat),pm2.
Partially Specializing a Hypothesis
After specialize h 1, the hypothesis becomes h : ∀ b, 1 + b = b + 1, which can then be applied to 2:
The obtain tactic is used when a hypothesis or proof term has internal structure that should be broken apart.
Where have introduces a single new fact into the context, obtain destructs a term into its pieces using pattern matching. For example, extracting the witness from an existential or the two sides of a conjunction.
The show tactic selects a goal whose target unifies with the given type and makes it the current goal.
When there is only one goal, it can be used like change to restate the goal in a definitionally equal form.
showt finds the first goal whose target unifies with t. It makes that the main goal,
performs the unification, and replaces the target with the unified version of t.
Selecting a Goal
When there are multiple goals, show brings a specific one to the front.
Here, after constructor the goals are ⊢ P then ⊢ Q, but show Q reorders them:
show_termtac runs tac, then prints the generated term in the form
"exact X Y Z" or "refine X ?_ Z" (prefixed by expose_names if necessary)
if there are remaining subgoals.
(For some tactics, the printed term will not be human readable.)
The tactics in this section make it easier avoid getting stuck on casts, which are functions that coerce data from one type to another, such as converting a natural number to the corresponding integer.
They are described in more detail by Lewis and Madelaine (2020)Robert Y. Lewis and Paul-Nicolas Madelaine, 2020. “Simplifying Casts and Coercions”. arXiv:2001.10594.
The tactic is basically a version of simp with a specific set of lemmas to move casts
upwards in the expression.
Therefore even in situations where non-terminal simp calls are discouraged (because of fragility),
norm_cast is considered to be safe.
It also has special handling of numerals.
There are also variants of basic tactics that use norm_cast to normalize expressions during
their operation, to make them more flexible about the expressions they accept
(we say that it is a tactic modulo the effects of norm_cast):
assumption_mod_cast for assumption.
This is effectively norm_castat*;assumption, but more efficient.
It normalizes casts in the goal and, for every hypothesis h in the context,
it will try to normalize casts in h and use exacth.
See also push_cast, which moves casts inwards rather than lifting them outwards.
push_cast rewrites the goal to move certain coercions (casts) inward, toward the leaf nodes.
This uses norm_cast lemmas in the forward direction.
For example, ↑(a+b) will be written to ↑a+↑b.
assumption_mod_cast is a variant of assumption that solves the goal
using a hypothesis. Unlike assumption, it first pre-processes the goal and
each hypothesis to move casts as far outwards as possible, so it can be used
in more situations.
Concretely, it runs norm_cast on the goal. For each local hypothesis h, it also
normalizes h with norm_cast and tries to use that to close the goal.
Lifts let and have expressions within a term as far out as possible.
It is like extract_lets+lift, but the top-level lets at the end of the procedure
are not extracted as local hypotheses.
clear_valuex... clears the values of the given local definitions.
A local definition x:α:=v becomes a hypothesis x:α.
clear_value(h:x=_) adds a hypothesis h:x=v before clearing the value of x.
This is short for haveh:x=v:=rfl;clear_valuex.
Any value definitionally equal to v can be used in place of _.
clear_value* clears values of all hypotheses that can be cleared.
Fails if none can be cleared.
These syntaxes can be combined. For example, clear_valuexy* ensures that x and y are cleared
while trying to clear all other local definitions,
and clear_value(hx:x=_)y*withhx does the same while first adding the hx:x=v hypothesis.
The ext tactic applies extensionality lemmas registered with the ext attribute.
Extensionality properties say that two objects are equal if they are equal under all appropriate observations.
For example, two functions are equal if they return the same value on every input and two pairs are equal if their components are equal.
See the section on function extensionality for quotients for more information.
Applies extensionality lemmas that are registered with the @[ext] attribute.
extpat* applies extensionality theorems as much as possible,
using the patterns pat* to introduce the variables in extensionality theorems using rintro.
For example, the patterns are used to name the variables introduced by lemmas such as funext.
Without patterns,ext applies extensionality lemmas as much
as possible but introduces anonymous hypotheses whenever needed.
extpat*:n applies ext theorems only up to depth n.
The ext1pat* tactic is like extpat* except that it only applies a single extensionality theorem.
Unused patterns will generate warning.
Patterns that don't match the variables will typically result in the introduction of anonymous hypotheses.
Function Extensionality
After ext n, the goal changes from an equality of functions to an equality of their values at an arbitrary n:
ext1pat* is like extpat* except that it only applies a single extensionality theorem rather
than recursively applying as many extensionality theorems as possible.
The pat* patterns are processed using the rintro tactic.
If no patterns are supplied, then variables are introduced anonymously using the intros tactic.
Apply function extensionality and introduce new hypotheses.
The tactic funext will keep applying the funext lemma until the goal target is not reducible to
|- ((fun x => ...) = (fun x => ...))
The variant funexth₁...hₙ applies funextn times, and uses the given identifiers to name the new hypotheses.
Patterns can be used like in the intro tactic. Example, given a goal
|- ((fun x : Nat × Bool => ...) = (fun x => ...))
funext(a,b) applies funext once and performs pattern matching on the newly introduced pair.
Proving Functions Equal with funext
After funext x, the goal reduces to showing f x = g x for an arbitrary x:
grind is a tactic inspired by modern SMT solvers. Picture a virtual whiteboard:
every time grind discovers a new equality, inequality, or logical fact,
it writes it on the board, groups together terms known to be equal,
and lets each reasoning engine read from and contribute to the shared workspace.
These engines work together to handle equality reasoning, apply known theorems,
propagate new facts, perform case analysis, and run specialized solvers
for domains like linear arithmetic and commutative rings.
grind is not designed for goals whose search space explodes combinatorially,
think large pigeonhole instances, graph‑coloring reductions, high‑order N‑queens boards,
or a 200‑variable Sudoku encoded as Boolean constraints. Such encodings require
thousands (or millions) of case‑splits that overwhelm grind’s branching search.
For bit‑level or combinatorial problems, consider using bv_decide.
bv_decide calls a state‑of‑the‑art SAT solver (CaDiCaL) and then returns a
compact, machine‑checkable certificate.
Equality reasoning
grind uses congruence closure to track equalities between terms.
When two terms are known to be equal, congruence closure automatically deduces
equalities between more complex expressions built from them.
For example, if a=b, then congruence closure will also conclude that fa = fb
for any function f. This forms the foundation for efficient equality reasoning in grind.
Here is an example:
To apply existing theorems, grind uses a technique called E-matching,
which finds matches for known theorem patterns while taking equalities into account.
Combined with congruence closure, E-matching helps grind discover
non-obvious consequences of theorems and equalities automatically.
The theorem gf asserts that g(fx)=x for all natural numbers x.
The attribute [grind=] instructs grind to use the left-hand side of the equation,
g(fx), as a pattern for E-matching.
Suppose we now have a goal involving:
example {a b} (h : f b = a) : g a = b := by
grind
Although ga is not an instance of the pattern g(fx),
it becomes one modulo the equation fb=a. By substituting a
with fb in ga, we obtain the term g(fb),
which matches the pattern g(fx) with the assignment x:=b.
Thus, the theorem gf is instantiated with x:=b,
and the new equality g(fb)=b is asserted.
grind then uses congruence closure to derive the implied equality
ga=g(fb) and completes the proof.
The pattern used to instantiate theorems affects the effectiveness of grind.
For example, the pattern g(fx) is too restrictive in the following case:
the theorem gf will not be instantiated because the goal does not even
contain the function symbol g.
example (h₁ : f b = a) (h₂ : f c = a) : b = c := by
grind
You can use the command grind_pattern to manually select a pattern for a given theorem.
In the following example, we instruct grind to use fx as the pattern,
allowing it to solve the goal automatically:
grind_pattern gf => f x
example {a b c} (h₁ : f b = a) (h₂ : f c = a) : b = c := by
grind
You can enable the option trace.grind.ematch.instance to make grind print a
trace message for each theorem instance it generates.
You can also specify a multi-pattern to control when grind should apply a theorem.
A multi-pattern requires that all specified patterns are matched in the current context
before the theorem is applied. This is useful for theorems such as transitivity rules,
where multiple premises must be simultaneously present for the rule to apply.
The following example demonstrates this feature using a transitivity axiom for a binary relation R:
By specifying the multi-pattern Rxy,Ryz, we instruct grind to
instantiate Rtrans only when both Rxy and Ryz are available in the context.
In the example, grind applies Rtrans to derive Rac from Rab and Rbc,
and can then repeat the same reasoning to deduce Rad from Rac and Rcd.
Instead of using grind_pattern to explicitly specify a pattern,
you can use the @[grind] attribute or one of its variants, which will use a heuristic to
generate a (multi-)pattern. The complete list is available in the reference manual. The main ones are:
@[grind→] will select a multi-pattern from the hypotheses of the theorem (i.e. it will use the theorem for forwards reasoning).
In more detail, it will traverse the hypotheses of the theorem from left-to-right, and each time it encounters a minimal indexable
(i.e. has a constant as its head) subexpression which "covers" (i.e. fixes the value of) an argument which was not
previously covered, it will add that subexpression as a pattern, until all arguments have been covered.
@[grind←] will select a multi-pattern from the conclusion of theorem (i.e. it will use the theorem for backwards reasoning).
This may fail if not all the arguments to the theorem appear in the conclusion.
@[grind] will traverse the conclusion and then the hypotheses left-to-right, adding patterns as they increase the coverage,
stopping when all arguments are covered.
@[grind=] checks that the conclusion of the theorem is an equality, and then uses the left-hand-side of the equality as a pattern.
This may fail if not all of the arguments appear in the left-hand-side.
Here is the previous example again but using the attribute [grind→]
To control theorem instantiation and avoid generating an unbounded number of instances,
grind uses a generation counter. Terms in the original goal are assigned generation zero.
When grind applies a theorem using terms of generation ≤ n, any new terms it creates
are assigned generation n + 1. This limits how far the tactic explores when applying
theorems and helps prevent an excessive number of instantiations.
Key options:
grind(ematch:=<num>) controls the number of E-matching rounds.
grind[<name>,...] instructs grind to use the declaration name during E-matching.
grindonly[<name>,...] is like grind[<name>,...] but does not use theorems tagged with @[grind].
grind can solve goals that reduce to linear integer arithmetic (LIA) using an
integrated decision procedure called lia. It understands
equalities p=0
inequalities p≤0
disequalities p≠0
divisibility d∣p
The solver incrementally assigns integer values to variables; when a partial
assignment violates a constraint it adds a new, implied constraint and retries.
This model-based search is complete for LIA.
Key options:
grind-lia disable the solver (useful for debugging)
grind+qlia accept rational models (shrinks the search space but is incomplete for ℤ)
Examples:
example{xy:Int}:2*x+4*y≠5:=byx:Inty:Int⊢ 2*x+4*y≠5grindAll goals completed! 🐙-- Mixing equalities and inequalities.example{xy:Int}:2*x+3*y=0→1≤x→y<1:=byx:Inty:Int⊢ 2*x+3*y=0→1≤x→y<1grindAll goals completed! 🐙-- Reasoning with divisibility.example(ab:Int):2∣a+1→2∣b+a→¬2∣b+2*a:=bya:Intb:Int⊢ 2∣a+1→2∣b+a→¬2∣b+2*agrindAll goals completed! 🐙example(xy:Int):27≤11*x+13*y→11*x+13*y≤45→-10≤7*x-9*y→7*x-9*y≤4→False:=byx:Inty:Int⊢ 27≤11*x+13*y→11*x+13*y≤45→-10≤7*x-9*y→7*x-9*y≤4→FalsegrindAll goals completed! 🐙-- Types that implement the `ToInt` type-class.example(abc:UInt64):a≤2→b≤3→c-a-b=0→c≤5:=bya:UInt64b:UInt64c:UInt64⊢ a≤2→b≤3→c-a-b=0→c≤5grindAll goals completed! 🐙
Algebraic solver (ring)
grind ships with an algebraic solver nick-named ring for goals that can
be phrased as polynomial equations (or disequations) over commutative rings,
semirings, or fields.
Works out of the box
All core numeric types and relevant Mathlib types already provide the required
type-class instances, so the solver is ready to use in most developments.
What it can decide:
equalities of the form p=q
disequalities p≠q
basic reasoning under field inverses (a/b:=a*b⁻¹)
goals that mix ring facts with other grind engines
Key options:
grind-ring turn the solver off (useful when debugging)
grind(ringSteps:=n) cap the number of steps performed by this procedure.
grind? takes the same arguments as grind, but reports an equivalent call to grindonly
that would be sufficient to close the goal. This is useful for reducing the size of the grind
theorems in a local invocation.
It is a implemented as a thin wrapper around the grind tactic, enabling only the lia solver.
Please use grind instead if you need additional capabilities.
grobner solves goals that can be phrased as polynomial equations (with further polynomial equations as hypotheses)
over commutative (semi)rings, using the Grobner basis algorithm.
It is a implemented as a thin wrapper around the grind tactic, enabling only the grobner solver.
Please use grind instead if you need additional capabilities.
The simp tactic uses lemmas and hypotheses to simplify the main goal target or
non-dependent hypotheses. It has many variants:
simp simplifies the main goal target using lemmas tagged with the attribute [simp].
simp[h₁,h₂,...,hₙ] simplifies the main goal target using the lemmas tagged
with the attribute [simp] and the given hᵢ's, where the hᵢ's are expressions.-
If an hᵢ is a defined constant f, then f is unfolded. If f has equational lemmas associated
with it (and is not a projection or a reducible definition), these are used to rewrite with f.
simp[*] simplifies the main goal target using the lemmas tagged with the
attribute [simp] and all hypotheses.
simponly[h₁,h₂,...,hₙ] is like simp[h₁,h₂,...,hₙ] but does not use [simp] lemmas.
simp[-id₁,...,-idₙ] simplifies the main goal target using the lemmas tagged
with the attribute [simp], but removes the ones named idᵢ.
simpath₁h₂...hₙ simplifies the hypotheses h₁:T₁ ... hₙ:Tₙ. If
the target or another hypothesis depends on hᵢ, a new simplified hypothesis
hᵢ is introduced, but the old one remains in the local context.
simpat* simplifies all the hypotheses and the target.
simp[*]at* simplifies target and all (propositional) hypotheses using the
other hypotheses.
simp! is shorthand for simp with autoUnfold:=true.
This will unfold applications of functions defined by pattern matching, when one of the patterns applies.
This can be used to partially evaluate many definitions.
simp? takes the same arguments as simp, but reports an equivalent call to simponly
that would be sufficient to close the goal. This is useful for reducing the size of the simp
set in a local invocation to speed up processing.
example(x:Nat):(ifTruethenx+2else3)=x+2:=byx:Nat⊢ (ifTruethenx+2else3)=x+2Try this:[apply]simp only [↓reduceIte, Nat.add_left_cancel_iff]simp?All goals completed! 🐙-- prints "Try this: simp only [ite_true]"
This command can also be used in simp_all and dsimp.
simp? takes the same arguments as simp, but reports an equivalent call to simponly
that would be sufficient to close the goal. This is useful for reducing the size of the simp
set in a local invocation to speed up processing.
example(x:Nat):(ifTruethenx+2else3)=x+2:=byx:Nat⊢ (ifTruethenx+2else3)=x+2Try this:[apply]simp only [↓reduceIte, Nat.add_left_cancel_iff]simp?All goals completed! 🐙-- prints "Try this: simp only [ite_true]"
This command can also be used in simp_all and dsimp.
simp_arith has been deprecated. It was a shorthand for simp+arith+decide.
Note that +decide is not needed for reducing arithmetic terms since simprocs have been added to Lean.
simp_arith! has been deprecated. It was a shorthand for simp!+arith+decide.
Note that +decide is not needed for reducing arithmetic terms since simprocs have been added to Lean.
The dsimp tactic is the definitional simplifier. It is similar to simp but only
applies theorems that hold by reflexivity. Thus, the result is guaranteed to be
definitionally equal to the input.
dsimp! is shorthand for dsimp with autoUnfold:=true.
This will unfold applications of functions defined by pattern matching, when one of the patterns applies.
This can be used to partially evaluate many definitions.
simp? takes the same arguments as simp, but reports an equivalent call to simponly
that would be sufficient to close the goal. This is useful for reducing the size of the simp
set in a local invocation to speed up processing.
example(x:Nat):(ifTruethenx+2else3)=x+2:=byx:Nat⊢ (ifTruethenx+2else3)=x+2Try this:[apply]simp only [↓reduceIte, Nat.add_left_cancel_iff]simp?All goals completed! 🐙-- prints "Try this: simp only [ite_true]"
This command can also be used in simp_all and dsimp.
simp? takes the same arguments as simp, but reports an equivalent call to simponly
that would be sufficient to close the goal. This is useful for reducing the size of the simp
set in a local invocation to speed up processing.
example(x:Nat):(ifTruethenx+2else3)=x+2:=byx:Nat⊢ (ifTruethenx+2else3)=x+2Try this:[apply]simp only [↓reduceIte, Nat.add_left_cancel_iff]simp?All goals completed! 🐙-- prints "Try this: simp only [ite_true]"
This command can also be used in simp_all and dsimp.
simp_all is a stronger version of simp[*]at* where the hypotheses and target
are simplified multiple times until no simplification is applicable.
Only non-dependent propositional hypotheses are considered.
simp_all! is shorthand for simp_all with autoUnfold:=true.
This will unfold applications of functions defined by pattern matching, when one of the patterns applies.
This can be used to partially evaluate many definitions.
simp? takes the same arguments as simp, but reports an equivalent call to simponly
that would be sufficient to close the goal. This is useful for reducing the size of the simp
set in a local invocation to speed up processing.
example(x:Nat):(ifTruethenx+2else3)=x+2:=byx:Nat⊢ (ifTruethenx+2else3)=x+2Try this:[apply]simp only [↓reduceIte, Nat.add_left_cancel_iff]simp?All goals completed! 🐙-- prints "Try this: simp only [ite_true]"
This command can also be used in simp_all and dsimp.
simp? takes the same arguments as simp, but reports an equivalent call to simponly
that would be sufficient to close the goal. This is useful for reducing the size of the simp
set in a local invocation to speed up processing.
example(x:Nat):(ifTruethenx+2else3)=x+2:=byx:Nat⊢ (ifTruethenx+2else3)=x+2Try this:[apply]simp only [↓reduceIte, Nat.add_left_cancel_iff]simp?All goals completed! 🐙-- prints "Try this: simp only [ite_true]"
This command can also be used in simp_all and dsimp.
simp_all_arith has been deprecated. It was a shorthand for simp_all+arith+decide.
Note that +decide is not needed for reducing arithmetic terms since simprocs have been added to Lean.
simp_all_arith! has been deprecated. It was a shorthand for simp_all!+arith+decide.
Note that +decide is not needed for reducing arithmetic terms since simprocs have been added to Lean.
This is a "finishing" tactic modification of simp. It has two forms.
simpa[rules,⋯]usinge will simplify the goal and the type of
e using rules, then try to close the goal using e.
Simplifying the type of e makes it more likely to match the goal
(which has also been simplified). This construction also tends to be
more robust under changes to the simp lemma set.
The final match between the simplified e and the simplified goal uses
reducible transparency, so it does not unfold semireducible definitions.
Write simpa[rules,⋯]using!e to perform the match at the ambient
(default/semireducible) transparency instead.
simpa[rules,⋯] will simplify the goal and the type of a
hypothesis this if present in the context, then try to close the goal using
the assumption tactic.
As with simp, the ! modifier after simpa enables auto-unfolding of
definitions in the simp set.
This is a "finishing" tactic modification of simp. It has two forms.
simpa[rules,⋯]usinge will simplify the goal and the type of
e using rules, then try to close the goal using e.
Simplifying the type of e makes it more likely to match the goal
(which has also been simplified). This construction also tends to be
more robust under changes to the simp lemma set.
The final match between the simplified e and the simplified goal uses
reducible transparency, so it does not unfold semireducible definitions.
Write simpa[rules,⋯]using!e to perform the match at the ambient
(default/semireducible) transparency instead.
simpa[rules,⋯] will simplify the goal and the type of a
hypothesis this if present in the context, then try to close the goal using
the assumption tactic.
As with simp, the ! modifier after simpa enables auto-unfolding of
definitions in the simp set.
This is a "finishing" tactic modification of simp. It has two forms.
simpa[rules,⋯]usinge will simplify the goal and the type of
e using rules, then try to close the goal using e.
Simplifying the type of e makes it more likely to match the goal
(which has also been simplified). This construction also tends to be
more robust under changes to the simp lemma set.
The final match between the simplified e and the simplified goal uses
reducible transparency, so it does not unfold semireducible definitions.
Write simpa[rules,⋯]using!e to perform the match at the ambient
(default/semireducible) transparency instead.
simpa[rules,⋯] will simplify the goal and the type of a
hypothesis this if present in the context, then try to close the goal using
the assumption tactic.
As with simp, the ! modifier after simpa enables auto-unfolding of
definitions in the simp set.
This is a "finishing" tactic modification of simp. It has two forms.
simpa[rules,⋯]usinge will simplify the goal and the type of
e using rules, then try to close the goal using e.
Simplifying the type of e makes it more likely to match the goal
(which has also been simplified). This construction also tends to be
more robust under changes to the simp lemma set.
The final match between the simplified e and the simplified goal uses
reducible transparency, so it does not unfold semireducible definitions.
Write simpa[rules,⋯]using!e to perform the match at the ambient
(default/semireducible) transparency instead.
simpa[rules,⋯] will simplify the goal and the type of a
hypothesis this if present in the context, then try to close the goal using
the assumption tactic.
As with simp, the ! modifier after simpa enables auto-unfolding of
definitions in the simp set.
Unfold definitions commonly used in well founded relation definitions.
Since Lean 4.12, Lean unfolds these definitions automatically before presenting the goal to the
user, and this tactic should no longer be necessary. Calls to simp_wf can be removed or replaced
by plain calls to simp.
The rw uses proofs of equality to rewrite goals and/or hypotheses, replacing occurrences of one of the equated terms with the other.
Given a proof of an equality h : x = y or an if-and-only-if h : P ↔ Q, it replaces occurrences of the left-hand side with the right-hand side in the goal.
Use rw [← h] to rewrite in the reverse direction, and rw [h] at hyp to rewrite in a hypothesis hyp rather than the goal.
After rewriting, rw automatically tries to close the goal with rfl.
Note that rw does not rewrite under binders such as ∀, ∃, or ∑.
For example, if h : a = b, then rw [h] will not rewrite occurrences of a inside ∀ x, f a x = g x.
rewrite[e] applies identity e as a rewrite rule to the target of the main goal.
If e is preceded by left arrow (← or <-), the rewrite is applied in the reverse direction.
If e is a defined constant, then the equational theorems associated with e are used.
This provides a convenient way to unfold e.
rewrite[e₁,...,eₙ] applies the given rules sequentially.
rewrite[e]atl rewrites e at location(s) l, where l is either * or a
list of hypotheses in the local context. In the latter case, a turnstile ⊢ or |-
can also be used, to signify the target of the goal.
Using rw(occs:=.posL)[e],
where L:ListNat, you can control which "occurrences" are rewritten.
(This option applies to each rule, so usually this will only be used with a single rule.)
Occurrences count from 1.
At each allowed occurrence, arguments of the rewrite rule e may be instantiated,
restricting which later rewrites can be found.
(Disallowed occurrences do not result in instantiation.)
(occs:=.negL) allows skipping specified occurrences.
erw[rules] is a shorthand for rw(transparency:=.default)[rules].
This does rewriting up to unfolding of regular definitions (by comparison to regular rw
which only unfolds @[reducible] definitions).
Controls which constants isDefEq (definitional equality) and whnf (weak head normal form)
are allowed to unfold.
Background: "try-hard" vs "speculative" modes
During type checking of user input, we assume the input is most likely correct, and we want
Lean to try hard before reporting a failure. Here, it is fine to unfold [semireducible] definitions
(the .default setting).
During proof automation (simp, rw, type class resolution), we perform many speculative
isDefEq calls — most of which fail. In this setting, we do not want to try hard: unfolding
too many definitions is a performance footgun. This is why .reducible exists.
The transparency hierarchy
The levels form a linear order: none<reducible<instances<default<all.
Each level unfolds everything the previous level does, plus more:
reducible: Only unfolds [reducible] definitions. Used for speculative isDefEq checks
(e.g., discrimination tree lookups in simp, type class resolution). Think of [reducible] as
[inline] for type checking and indexing.
instances: Also unfolds [implicit_reducible] definitions. Instance diamonds are common:
for example, AddNat can come from a direct instance or via Semiring. These instances are all
definitionally equal but structurally different, so isDefEq must unfold them to confirm equality.
This level also handles definitions used in types that appear in implicit arguments (e.g.,
Nat.add, Array.size). However, these definitions must not be eagerly reduced (instances
become huge terms), and discrimination trees do not index them. This makes .instances safe for
speculative checks involving implicit arguments without the performance cost of .default.
default: Also unfolds [semireducible] definitions (anything not [irreducible]).
Used for type checking user input where we want to try hard.
all: Also unfolds [irreducible] definitions. Rarely used.
Implicit arguments and transparency
When proof automation (e.g., simp, rw) applies a lemma, explicit arguments are checked at the
caller's transparency (typically .reducible). But implicit arguments are often "invisible" to the
user — if a lemma fails to apply because of an implicit argument mismatch, the user is confused.
Historically, Lean bumped transparency to .default for implicit arguments, but this eventually
became a performance bottleneck in Mathlib. The option backward.isDefEq.respectTransparency
(default: true) disables this bump. Instead, instance-implicit arguments ([..]) are checked at
.instances, and other implicit arguments are checked at the caller's transparency.
See also: ReducibilityStatus, backward.isDefEq.respectTransparency,
backward.whnf.reducibleClassField.
Unfolds all constants except those tagged as @[irreducible]. Used for type checking
user-written terms where we expect the input to be correct and want to try hard.
Unfolds only constants tagged with the @[reducible] attribute. Used for speculative
isDefEq in proof automation (simp, rw, type class resolution) where most checks fail
and we must not try too hard.
Unfolds reducible constants and constants tagged with @[implicit_reducible].
Used for checking implicit arguments during proof automation, and for unfolding
class projections applied to instances. Instance diamonds (e.g., AddNat from a direct instance
vs from Semiring) are definitionally equal but structurally different, so isDefEq must unfold
them. Also handles definitions used in types of implicit arguments (e.g., Nat.add, Array.size).
Definitions can be either global or local definitions.
For non-recursive global definitions, this tactic is identical to delta.
For recursive global definitions, it uses the "unfolding lemma" id.eq_def,
which is generated for each recursive definition, to unfold according to the recursive definition given by the user.
Only one level of unfolding is performed, in contrast to simponly[id], which unfolds definition id recursively.
deltaid1id2... delta-expands the definitions id1, id2, ....
This is a low-level tactic, it will expose how recursive definitions have been
compiled by Lean.
The constructor tactic tries to solve the goal by application of a constructor.
When the goal's target type has a single constructor, it replaces the goal with one subgoal for each of the constructor's arguments.
This is commonly used to split a goal of the form P ∧ Q into separate goals for P and Q, or to split P ↔ Q into the two implications.
It is essentially syntactic sugar for refine ⟨?_, ?_, …⟩ with as many holes as the constructor has arguments.
The injection tactic is based on the fact that constructors of inductive data
types are injections.
That means that if c is a constructor of an inductive datatype, and if (ct₁)
and (ct₂) are two terms that are equal then t₁ and t₂ are equal too.
If q is a proof of a statement of conclusion t₁=t₂, then injection applies
injectivity to derive the equality of all arguments of t₁ and t₂ placed in
the same positions. For example, from (a::b)=(c::d) we derive a=c and b=d.
To use this tactic t₁ and t₂ should be constructor applications of the same constructor.
Given h:a::b=c::d, the tactic injectionh adds two new hypothesis with types
a=c and b=d to the main goal.
The tactic injectionhwithh₁h₂ uses the names h₁ and h₂ to name the new hypotheses.
injections applies injection to all hypotheses recursively
(since injection can produce new hypotheses). Useful for destructing nested
constructor equalities like (a::b::c)=(d::e::f).
The left and right tactics select which side of a disjunction to prove.
Elimination tactics use recursors and the automatically-derived casesOn helper to implement induction and case splitting.
The subgoals that result from these tactics are determined by the types of the minor premises of the eliminators, and using different eliminators with the using option results in different subgoals.
Choosing Eliminators
When attempting to prove that ∀(i:Fin(n+1)),0+i=i, after introducing the hypotheses the tactic inductionimkn:Natval✝:NatisLt✝:val✝<n+1⊢ 0+⟨val✝,isLt✝⟩=⟨val✝,isLt✝⟩ results in:
Custom eliminators can be registered using the induction_eliminator and cases_eliminator attributes.
The eliminator is registered for its explicit targets (i.e. those that are explicit, rather than implicit, parameters to the eliminator function) and will be applied when induction or cases is used on targets of those types.
When present, custom eliminators take precedence over recursors.
Setting tactic.customEliminators to false disables the use of custom eliminators.
attributeCustom Eliminators
The induction_eliminator attribute registers an eliminator for use by the induction tactic.
attr::= ...
|induction_eliminator
The cases_eliminator attribute registers an eliminator for use by the cases tactic.
attr::= ...
|cases_eliminator
The cases tactic performs case analysis on a term in the local context.
It decomposes the term according to the constructors of its inductive type, creating one subgoal for each constructor.
For a hypothesis h : P ∧ Q, this yields its two components; for h : P ∨ Q, it creates two separate goals; for a natural number n, it splits into the zero and succ cases.
Assuming x is a variable in the local context with an inductive type,
casesx splits the main goal, producing one goal for each constructor of the
inductive type, in which the target is replaced by a general instance of that constructor.
If the type of an element in the local context depends on x,
that element is reverted and reintroduced afterward,
so that the case split affects that hypothesis as well.
cases detects unreachable cases and closes them automatically.
For example, given n:Nat and a goal with a hypothesis h:Pn and target Qn,
casesn produces one goal with hypothesis h:P0 and target Q0,
and one goal with hypothesis h:P(Nat.succa) and target Q(Nat.succa).
Here the name a is chosen automatically and is not accessible.
You can use with to provide the variables names for each constructor.
casese, where e is an expression instead of a variable, generalizes e in the goal,
and then cases on the resulting variable.
Given as:Listα, casesaswith|nil=>tac₁|consaas'=>tac₂,
uses tactic tac₁ for the nil case, and tac₂ for the cons case,
and a and as' are used as names for the new variables introduced.
casesh:e, where e is a variable or an expression,
performs cases on e as above, but also adds a hypothesis h:e=... to each goal,
where ... is the constructor instance for that particular case.
The rcases tactic is a recursive version of cases that destructs a hypothesis using pattern matching notation.
It uses ⟨x, y⟩ for constructor patterns and (x | y) for disjunctive patterns, and these can be nested.
rcases is a tactic that will perform cases recursively, according to a pattern. It is used to
destructure hypotheses or expressions composed of inductive types like h1:a∧b∧c∨d or
h2:∃xy,trans_relRxy. Usual usage might be rcasesh1with⟨ha,hb,hc⟩|hd or
rcasesh2with⟨x,y,_|⟨z,hxz,hzy⟩⟩ for these examples.
Each element of an rcases pattern is matched against a particular local hypothesis (most of which
are generated during the execution of rcases and represent individual elements destructured from
the input expression). An rcases pattern has the following grammar:
A name like x, which names the active hypothesis as x.
A blank _, which does nothing (letting the automatic naming system used by cases name the
hypothesis).
A hyphen -, which clears the active hypothesis and any dependents.
The keyword rfl, which expects the hypothesis to be h:a=b, and calls subst on the
hypothesis (which has the effect of replacing b with a everywhere or vice versa).
A type ascription p:ty, which sets the type of the hypothesis to ty and then matches it
against p. (Of course, ty must unify with the actual type of h for this to work.)
A tuple pattern ⟨p1,p2,p3⟩, which matches a constructor with many arguments, or a series
of nested conjunctions or existentials. For example if the active hypothesis is a∧b∧c,
then the conjunction will be destructured, and p1 will be matched against a, p2 against b
and so on.
A @ before a tuple pattern as in @⟨p1,p2,p3⟩ will bind all arguments in the constructor,
while leaving the @ off will only use the patterns on the explicit arguments.
An alternation pattern p1|p2|p3, which matches an inductive type with multiple constructors,
or a nested disjunction like a∨b∨c.
A pattern like ⟨a,b,c⟩|⟨d,e⟩ will do a split over the inductive datatype,
naming the first three parameters of the first constructor as a,b,c and the
first two of the second constructor d,e. If the list is not as long as the
number of arguments to the constructor or the number of constructors, the
remaining variables will be automatically named. If there are nested brackets
such as ⟨⟨a⟩,b|c⟩|d then these will cause more case splits as necessary.
If there are too many arguments, such as ⟨a,b,c⟩ for splitting on
∃x,∃y,px, then it will be treated as ⟨a,⟨b,c⟩⟩, splitting the last
parameter as necessary.
rcases also has special support for quotient types: quotient induction into Prop works like
matching on the constructor quot.mk.
rcasesh:ewithPAT will do the same as rcasesewithPAT with the exception that an
assumption h:e=PAT will be added to the context.
Nested Destructuring
The pattern ⟨hp, hq, hr⟩ destructs the nested conjunction P ∧ Q ∧ R in one step:
The fun_cases tactic is a convenience wrapper of the cases tactic when using a functional
cases principle.
The tactic invocation
fun_cases f x ... y ...`
is equivalent to
cases y, ... using f.fun_cases_unfolding x ...
where the arguments of f are used as arguments to f.fun_cases_unfolding or targets of the case
analysis, as appropriate.
The form
fun_casesf
(with no arguments to f) searches the goal for a unique eligible application of f, and uses
these arguments. An application of f is eligible if it is saturated and the arguments that will
become targets are free variables.
The form fun_casesfxywith|case1=>tac₁|case2x'ih=>tac₂ works like with cases.
Under set_optiontactic.fun_induction.unfoldingtrue (the default), fun_induction uses the
f.fun_cases_unfolding theorem, which will try to automatically unfold the call to f in
the goal. With set_optiontactic.fun_induction.unfoldingfalse, it uses f.fun_cases instead.
The induction tactic performs mathematical induction.
Like cases, it splits into cases, but it additionally provides an inductive hypothesis in each recursive case.
This makes induction a useful tool for proving properties of recursive data, especially natural numbers.
Assuming x is a variable in the local context with an inductive type,
inductionx applies induction on x to the main goal,
producing one goal for each constructor of the inductive type,
in which the target is replaced by a general instance of that constructor
and an inductive hypothesis is added for each recursive argument to the constructor.
If the type of an element in the local context depends on x,
that element is reverted and reintroduced afterward,
so that the inductive hypothesis incorporates that hypothesis as well.
For example, given n:Nat and a goal with a hypothesis h:Pn and target Qn,
inductionn produces one goal with hypothesis h:P0 and target Q0,
and one goal with hypotheses h:P(Nat.succa) and ih₁:Pa→Qa and target Q(Nat.succa).
Here the names a and ih₁ are chosen automatically and are not accessible.
You can use with to provide the variables names for each constructor.
inductione, where e is an expression instead of a variable,
generalizes e in the goal, and then performs induction on the resulting variable.
inductioneusingr allows the user to specify the principle of induction that should be used.
Here r should be a term whose result type must be of the form Ct,
where C is a bound variable and t is a (possibly empty) sequence of bound variables
inductionegeneralizingz₁...zₙ, where z₁...zₙ are variables in the local context,
generalizes over z₁...zₙ before applying the induction but then introduces them in each goal.
In other words, the net effect is that each inductive hypothesis is generalized.
Given x:Nat, inductionxwith|zero=>tac₁|succx'ih=>tac₂
uses tactic tac₁ for the zero case, and tac₂ for the succ case.
Induction on Natural Numbers
Here we use induction to establish that zero is the identity for addition on the left.
The base case zero is closed by rfl, and the successor case uses the inductive hypothesis ih : 0 + n = n:
The fun_induction tactic is a convenience wrapper around the induction tactic to use the
functional induction principle.
The tactic invocation
fun_inductionfx₁...xₙy₁...yₘ
where f is a function defined by non-mutual structural or well-founded recursion, is equivalent to
induction y₁, ... yₘ using f.induct_unfolding x₁ ... xₙ
where the arguments of f are used as arguments to f.induct_unfolding or targets of the
induction, as appropriate.
The form
fun_inductionf
(with no arguments to f) searches the goal for a unique eligible application of f, and uses
these arguments. An application of f is eligible if it is saturated and the arguments that will
become targets are free variables.
Under set_optiontactic.fun_induction.unfoldingtrue (the default), fun_induction uses the
f.induct_unfolding induction principle, which will try to automatically unfold the call to f in
the goal. With set_optiontactic.fun_induction.unfoldingfalse, it uses f.induct instead.
The tactic nofun is shorthand for exactnofun: it introduces the assumptions, then performs an
empty pattern match, closing the goal if the introduced pattern is impossible.
The library search tactics are intended for interactive use.
When run, they search the Lean library for lemmas or rewrite rules that could be applicable in the current situation, and suggests a new tactic.
These tactics should not be left in a proof; rather, their suggestions should be incorporated.
Searches environment for definitions or theorems that can solve the goal using exact
with conditions resolved by solve_by_elim.
The optional using clause provides identifiers in the local context that must be
used by exact? when closing the goal. This is most useful if there are multiple
ways to resolve the goal, and one wants to guide which lemma is used.
Use +grind to enable grind as a fallback discharger for subgoals.
Use +try? to enable try? as a fallback discharger for subgoals.
Use -star to disable fallback to star-indexed lemmas (like Empty.elim, And.left).
Use +all to collect all successful lemmas instead of stopping at the first.
Searches environment for definitions or theorems that can refine the goal using apply
with conditions resolved when possible with solve_by_elim.
The optional using clause provides identifiers in the local context that must be
used when closing the goal.
Use +grind to enable grind as a fallback discharger for subgoals.
Use +try? to enable try? as a fallback discharger for subgoals.
Use -star to disable fallback to star-indexed lemmas.
Use +all to collect all successful lemmas instead of stopping at the first.
The split tactic is useful for breaking nested if-then-else and match expressions into separate cases.
For a match expression with n cases, the split tactic generates at most n subgoals.
For example, given n:Nat, and a target ifn=0thenQelseR, split will generate
one goal with hypothesis n=0 and target Q, and a second goal with hypothesis
¬n=0 and target R. Note that the introduced hypothesis is unnamed, and is commonly
renamed using the case or next tactics.
The by_cases tactic splits the proof into two cases based on whether a proposition is true or not.
The hypothesis can optionally be named with h : P syntax.
The decide tactic closes goals whose truth is decidable in the sense that it
can be determined by computation, such as concrete arithmetic facts or membership in finite structures.
decide attempts to prove the main goal (with target type p) by synthesizing an instance of Decidablep
and then reducing that instance to evaluate the truth value of p.
If it reduces to isTrueh, then h is a proof of p that closes the goal.
The target is not allowed to contain local variables or metavariables.
If there are local variables, you can first try using the revert tactic with these local variables to move them into the target,
or you can use the +revert option, described below.
Options:
decide+revert begins by reverting local variables that the target depends on,
after cleaning up the local context of irrelevant variables.
A variable is relevant if it appears in the target, if it appears in a relevant variable,
or if it is a proposition that refers to a relevant variable.
decide+kernel uses kernel for reduction instead of the elaborator.
It has two key properties: (1) since it uses the kernel, it ignores transparency and can unfold everything,
and (2) it reduces the Decidable instance only once instead of twice.
decide+native uses the native code compiler (#eval) to evaluate the Decidable instance,
admitting the result via an axiom. This can be significantly more efficient than using reduction, but it is at the cost of increasing the size
This can be significantly more efficient than using reduction, but it is at the cost of increasing the size
of the trusted code base.
Namely, it depends on the correctness of the Lean compiler and all definitions with an @[implemented_by] attribute.
Like with +kernel, the Decidable instance is evaluated only once.
Limitation: In the default mode or +kernel mode, since decide uses reduction to evaluate the term,
Decidable instances defined by well-founded recursion might not work because evaluating them requires reducing proofs.
Reduction can also get stuck on Decidable instances with Eq.rec terms.
These can appear in instances defined using tactics (such as rw and simp).
To avoid this, create such instances using definitions such as decidable_of_iff instead.
example : 1 ≠ 1 := by decide
/-
tactic 'decide' proved that the proposition
1 ≠ 1
is false
-/
Trying to prove a proposition whose Decidable instance fails to reduce
opaque unknownProp : Prop
open scoped Classical in
example : unknownProp := by decide
/-
tactic 'decide' failed for proposition
unknownProp
since its 'Decidable' instance reduced to
Classical.choice ⋯
rather than to the 'isTrue' constructor.
-/
Properties and relations
For equality goals for types with decidable equality, usually rfl can be used in place of decide.
Because decide runs the decision procedure using the kernel's term reduction, it can be extremely slow or time out on large problems.
For example, checking Nat.Prime 104729 with decide would take impractically long.
For arithmetic goals involving large numbers, grind, or norm_num are more performant.
native_decide is a synonym for decide+native.
It will attempt to prove a goal of type p by synthesizing an instance
of Decidablep and then evaluating it to isTrue... Unlike decide, this
uses #eval to evaluate the decidability instance.
This should be used with care because it adds the entire lean compiler to the trusted
part, and a new axiom will show up in #printaxioms for theorems using
this method or anything that transitively depends on them. Nevertheless, because it is
compiled, this can be significantly more efficient than using decide, and for very
large computations this is one way to run external programs and trust the result.
The omega tactic, for resolving integer and natural linear arithmetic problems.
It is not yet a full decision procedure (no "dark" or "grey" shadows),
but should be effective on many problems.
We handle hypotheses of the form x=y, x<y, x≤y, and k∣x for xy in Nat or Int
(and k a literal), along with negations of these statements.
We decompose the sides of the inequalities as linear combinations of atoms.
If we encounter x/k or x%k for literal integers k we introduce new auxiliary variables
and the relevant inequalities.
On the first pass, we do not perform case splits on natural subtraction.
If omega fails, we recursively perform a case split on
a natural subtraction appearing in a hypothesis, and try again.
bv_omega is omega with an additional preprocessor that turns statements about BitVec into statements about Nat.
Currently the preprocessor is implemented as trysimponly[bitvec_to_nat]at*.
bitvec_to_nat is a @[simp] attribute that you can (cautiously) add to more theorems.
Close fixed-width BitVec and Bool goals by obtaining a proof from an external SAT solver and
verifying it inside Lean. The solvable goals are currently limited to
If bv_decide encounters an unknown definition it will be treated like an unconstrained BitVec
variable. Sometimes this enables solving goals despite not understanding the definition because
the precise properties of the definition do not matter in the specific proof.
If bv_decide fails to close a goal it provides a counter-example, containing assignments for all
terms that were considered as variables.
In order to avoid calling a SAT solver every time, the proof can be cached with bv_decide?.
If solving your problem relies inherently on using associativity or commutativity, consider enabling
the bv.ac_nf option.
Note: bv_decide trusts the correctness of the code generator and adds a axioms asserting its result.
This tactic works just like bv_decide but skips calling a SAT solver by using a proof that is
already stored on disk. It is called with the name of an LRAT file in the same directory as the
current Lean file:
The cbv tactic simulates call-by-value evaluation to reduce terms.
In call-by-value evaluation, the arguments to a function are reduced to values before the function call is reduced.
Roughly speaking, values are either functions or applications of constructors to values; the body of a function does not need to be a value for the function itself to count as a value.
This evaluation strategy matches the execution order of code produced by the Lean compiler, which makes it a good match for code that is written to perform well at run time.
cbv unfolds definitions using their equational lemmas and applies similar theorems that are automatically proved for matcher functions, producing propositional equality proofs at each step.
Because the unfolding is propositional rather than definitional, cbv can reduce functions defined via well-founded recursion or partial fixpoints.
In general, these functions are not definitionally equal to their unfoldings, so the kernel's definitional reduction does not reduce their recursive calls.
The proofs produced by cbv only use the three standard axioms (propext, Quot.sound, and Classical.choice).
In particular, they do not require trust in the correctness of the code generator, unlike native_decide.
Because cbv rewrites subterms via congrArg and congrFun, it cannot rewrite subterms that appear in dependent positions.
Rewriting the argument of a dependent function would change the type of subsequent arguments, and even with heterogeneous equality there are no suitable congruence lemmas for arbitrary dependent functions.
When reducing constant applications, cbv tries the following strategies in order:
Declarations marked with cbv_opaque are never unfolded unless a matching cbv_eval rewrite rule is provided.
syntaxCall-by-Value Evaluation
tactic::= ...
|`cbv` performs simplification that closely mimics call-by-value evaluation.
It reduces terms by unfolding definitions using their defining equations and
applying matcher equations. The unfolding is propositional, so `cbv` also works
with functions defined via well-founded recursion or partial fixpoints.
`cbv` reduces the goal type (and optionally hypothesis types) using call-by-value
evaluation. For equation goals (`lhs = rhs`), `cbv` automatically attempts `refl`
after reduction to close the goal.
`cbv` supports the standard `at` location syntax:
- `cbv` — reduce the goal target
- `cbv at h` — reduce hypothesis `h`
- `cbv at h |-` — reduce hypothesis `h` and the goal target
- `cbv at *` — reduce all non-dependent propositional hypotheses and the goal target
`cbv` is not a finishing tactic in general: it may leave a new (simpler) goal.
The proofs produced by `cbv` only use the three standard axioms.
In particular, they do not require trust in the correctness of the code
generator.
cbv(Location specifications are used by many tactics that can operate on either the
hypotheses or the goal. It can have one of the forms:
* 'empty' is not actually present in this syntax, but most tactics use
`(location)?` matchers. It means to target the goal only.
* `at h₁ ... hₙ`: target the hypotheses `h₁`, ..., `hₙ`
* `at h₁ h₂ ⊢`: target the hypotheses `h₁` and `h₂`, and the goal
* `at *`: target all hypotheses and the goal
atA sequence of one or more locations at which a tactic should operate. These can include local
hypotheses and `⊢`, which denotes the goal.
(term|The `⊢` location refers to the current goal. locationType)*)?
cbv performs simplification that closely mimics call-by-value evaluation.
It reduces terms by unfolding definitions using their defining equations and
applying matcher equations. The unfolding is propositional, so cbv also works
with functions defined via well-founded recursion or partial fixpoints.
cbv reduces the goal type (and optionally hypothesis types) using call-by-value
evaluation. For equation goals (lhs=rhs), cbv automatically attempts refl
after reduction to close the goal.
cbvath|- — reduce hypothesis h and the goal target
cbvat* — reduce all non-dependent propositional hypotheses and the goal target
cbv is not a finishing tactic in general: it may leave a new (simpler) goal.
The proofs produced by cbv only use the three standard axioms.
In particular, they do not require trust in the correctness of the code
generator.
Reducing Well-Founded Recursive Functions
The function countdown is defined using well-founded recursion, so it is not definitionally equal to its unfolding.
Ordinary rfl cannot close the goal:
The cbv tactic supports the standard at location syntax.
When used with at h, it reduces the type of hypothesis h.
When used with at *, it reduces all non-dependent propositional
hypotheses and the goal target.
Unlike decide, cbv is not a terminal tactic.
It simplifies the goal as much as possible but may leave a goal that requires further reasoning.
Here, cbv reduces the call to countdown but leaves the membership goal:
decide_cbv is a finishing tactic that closes goals of the form p, where p
is a Decidable proposition. It proceeds in two steps:
Apply of_decide_eq_true to transform the goal into decidep=true.
Reduce decidep via call-by-value normalization. If the result is
definitionally equal to true, the goal is closed.
decide_cbv fails with an error if decidep does not reduce to true.
Unlike cbv, decide_cbv is a terminal tactic: it either closes the goal or
fails.
The proofs produced by decide_cbv only use the three standard axioms.
In particular, they do not require trust in the correctness of the code
generator.
Because decide_cbv uses propositional unfolding, it can evaluate complex decision procedures involving well-founded recursive functions.
Here, Nat.minFac finds the smallest divisor of a number, while the helper minFacAux searches for the smallest odd divisor:
The cbv_eval attribute registers a theorem as a custom rewrite rule that cbv applies before trying equational lemmas.
The theorem must be an unconditional equality; one side (generally the left-hand side) must be an application of a constant.
attr::= ...
|Register a theorem as a rewrite rule for `cbv` evaluation of a given definition.
You can instruct `cbv` to rewrite the lemma from right-to-left:
```lean
@[cbv_eval ←] theorem my_thm : rhs = lhs := ...
```
cbv_eval
The ← modifier instructs cbv to apply the rule from right to left:
attr::= ...
|Register a theorem as a rewrite rule for `cbv` evaluation of a given definition.
You can instruct `cbv` to rewrite the lemma from right-to-left:
```lean
@[cbv_eval ←] theorem my_thm : rhs = lhs := ...
```
cbv_eval←
cbv_eval
Custom rewrite rules can be used to control how cbv evaluates specific functions.
For instance, the naïve definition of reversal, slowReverse, has quadratic complexity due to repeated use of List.append.
By providing a tail-recursive characterization via fastReverse, cbv can evaluate slowReverse efficiently:
The cbv_opaque attribute prevents cbv from unfolding a declaration using its equational lemmas or unfold theorems.
However, cbv_eval rewrite rules always take priority over cbv_opaque: if a matching cbv_eval rule exists for a declaration, it will be applied even if the declaration is marked cbv_opaque.
This allows replacing the default unfolding behavior with a controlled set of evaluation rules.
attr::= ...
|cbv_opaque
Opaque Definitions with @[cbv_opaque]
Marking countdown as cbv_opaque prevents cbv from unfolding it, so the goal that was previously closed by cbv now remains unsolved:
A cbv simplification procedure (cbv simproc) is a user-defined metaprogram that cbv invokes on subexpressions matching a given pattern.
While cbv_eval rules are limited to static equality, cbv simprocs can perform arbitrary computation to decide how to rewrite a subexpression.
Common use cases include defining procedures for evaluating functions on literal values or short-circuiting control flow.
The simprocs used by cbv have type Lean.Meta.Sym.Simp.Simproc, which is distinct from the Lean.Meta.Simp.Simproc type used by the simp tactic.
The two systems are independent: registering a cbv simproc has no effect on simp, and vice versa.
syntaxCustom cbv Simplification Procedures
The body must have type Simproc (that is, Expr→SimpMResult).
The pattern is an expression with holes (_) that determines which subexpressions trigger the procedure.
Patterns are matched agains subexpressions structurally after unfolding reducible definitions and applying β-, η-, and ζ-reduction to both sides.
Matching is modulo α-equivalence (bound variable names are ignored), and proof and instance arguments in the pattern are treated as wildcards.
An optional phase specifier controls when the procedure fires during normalization.
When no phase is specified, the default is ↑ (post).
↓ (pre)
Fires on each subexpression beforecbv reduces it. The arguments are still unreduced. Use this phase to override cbv's default call-by-value evaluation order. A typical use case would be to evaluate arguments lazily or to short-circuit evaluation (as the built-in ite and Or procedures do).
cbv_eval (eval)
Fires after arguments have been reduced to values, but before the function is unfolded. Use this phase to provide efficient ground evaluation procedures.
↑ (post, default)
Fires aftercbv has attempted standard reduction (equation lemmas, unfolding, kernel matching). Use this phase when standard reduction should be tried first.
command::= ...
|A user-defined simplification procedure used by the `cbv` tactic.
The body must have type `Lean.Meta.Sym.Simp.Simproc` (`Expr → SimpM Result`).
Procedures are indexed by a discrimination tree pattern and fire at one of three phases:
`↓` (pre), `cbv_eval` (eval), or `↑` (post, default).
`attrKind` matches `("scoped" <|> "local")?`, used before an attribute like `@[local simp]`. cbv_simprocname(pattern):=body
An optional phase specifier can be placed before the name:
command::= ...
|A user-defined simplification procedure used by the `cbv` tactic.
The body must have type `Lean.Meta.Sym.Simp.Simproc` (`Expr → SimpM Result`).
Procedures are indexed by a discrimination tree pattern and fire at one of three phases:
`↓` (pre), `cbv_eval` (eval), or `↑` (post, default).
`attrKind` matches `("scoped" <|> "local")?`, used before an attribute like `@[local simp]`. cbv_simprocUse this rewrite rule before entering the subterms ↓name(pattern):=body
command::= ...
|A user-defined simplification procedure used by the `cbv` tactic.
The body must have type `Lean.Meta.Sym.Simp.Simproc` (`Expr → SimpM Result`).
Procedures are indexed by a discrimination tree pattern and fire at one of three phases:
`↓` (pre), `cbv_eval` (eval), or `↑` (post, default).
`attrKind` matches `("scoped" <|> "local")?`, used before an attribute like `@[local simp]`. cbv_simproccbv_evalname(pattern):=body
The cbv_simproc_decl variant declares the procedure without activating it.
It can be activated later with cbv_simproc.
command::= ...
|A `cbv_simproc` declaration without automatically adding it to the cbv simproc set.
To activate, use `attribute [cbv_simproc]`.
cbv_simproc_declname(pattern):=body
attributeSimplification Procedure Attribute for cbv
The cbv_simproc attribute activates a previously declared simplification procedure (defined with cbv_simproc_decl) for use by cbv.
An optional phase specifier controls when the procedure fires during normalization.
attr::= ...
|cbv_simproc
Phase specifiers control when the procedure fires:
attr::= ...
|cbv_simprocUse this rewrite rule before entering the subterms ↓
attr::= ...
|cbv_simprocUse this rewrite rule after entering the subterms ↑
attr::= ...
|cbv_simproccbv_eval
Declaring a cbv_simproc
A simplification procedure is declared by providing a pattern and a body of type Lean.Meta.Sym.Simp.Simproc.
The pattern is an expression with holes (_) that determines which subexpressions trigger the procedure.
Here, the pattern is (myConst _), which matches any application of myConst.
The procedure (fun_e=>doreturn.rfl) ignores the expression, returning a result that indicates that no rewriting is to be performed.
opaquemyConst:Nat→NatopenLeanMetaSym.Simpincbv_simprocevalMyConst(myConst_):=fun_e=>do-- A real simproc would inspect `e`, compute a result,-- and return `.step result proof`.return.rfl
The Lean.Parser.«command_Cbv_simproc_decl_(_):=_» : commandA `cbv_simproc` declaration without automatically adding it to the cbv simproc set.
To activate, use `attribute [cbv_simproc]`.
cbv_simproc_decl variant declares the procedure without activating it.
The cbv_simproc attribute can be used to activate it later, optionally at a specific phase:
This is an example of a pre-phase simplification procedure that breaks the conventional call-by-value order of evaluation to achieve laziness.
The ↓ modifier ensures that evalListHead fires before the arguments to List.head? are evaluated.
It rewrites List.head?(a::as) to somea using List.head?_cons, discarding the tail as without evaluating it.
Only the head element a is subsequently reduced by cbv.
Inspecting the proof term confirms that the simplification procedure fired: List.head?_cons appears directly in the proof, showing that cbv used the simproc's rewrite rather than reducing List.head? by unfolding its definition.
Lean includes a number of built-in simplification procedures for cbv.
These handle control flow (ite, dite, cond, Decidable.decide, Decidable.rec), logical connectives (Or, And), and data structure operations (array indexing, string operations).
The control flow procedures use the ↓ (pre) phase to enable short-circuit evaluation, while the array and string procedures use the cbv_eval phase to reduce ground applications directly.
with_reducible_and_instancestacs executes tacs using the .instances transparency setting.
In this setting only definitions tagged as [reducible] or type class instances are unfolded.
guard_expre=e' checks that e and e' are defeq at reducible transparency.
guard_expre=~e' checks that e and e' are defeq at default transparency.
guard_expre=ₛe' checks that e and e' are syntactically equal.
guard_expre=ₐe' checks that e and e' are alpha-equivalent.
Both e and e' are elaborated then have their metavariables instantiated before the equality
check. Their types are unified (using isDefEqGuarded) before synthetic metavariables are
processed, which helps with default instance handling.
stop is a helper tactic for "discarding" the rest of a proof:
it is defined as repeatsorry.
It is useful when working on the middle of a complex proofs,
and less messy than commenting the remainder of the proof.
Constructs a proof of decreasing along a well founded relation, by simplifying, then applying
lexicographic order lemmas and finally using ts to solve the base case. If it fails,
it prints a message to help the user diagnose an ill-founded recursive definition.
get_elem_tactic is the tactic automatically called by the notation arr[i]
to prove any side conditions that arise when constructing the term
(e.g. the index is in bounds of the array). It just delegates to
get_elem_tactic_extensible and gives a diagnostic error message otherwise;
users are encouraged to extend get_elem_tactic_extensible instead of this tactic.
The sorry tactic is a temporary placeholder for an incomplete tactic proof,
closing the main goal using exactsorry.
This is intended for stubbing-out incomplete parts of a proof while still having a syntactically correct proof skeleton.
Lean will give a warning whenever a proof uses sorry, so you aren't likely to miss it,
but you can double check if a theorem depends on sorry by looking for sorryAx in the output
of the #printaxiomsmy_thm command, the axiom used by the implementation of sorry.
trivial tries different simple tactics (e.g., rfl, contradiction, ...)
to close the current goal.
You can use the command macro_rules to extend the set of tactics used. Example:
expose_names renames all inaccessible variables with accessible names, making them available
for reference in generated tactics. However, this renaming introduces machine-generated names
that are not fully under user control. expose_names is primarily intended as a preamble for
auto-generated end-game tactic scripts. It is also useful as an alternative to
set_optiontactic.hygienicfalse. If explicit control over renaming is needed in the
middle of a tactic script, consider using structured tactic scripts with
match..with, induction..with, or intro with explicit user-defined names,
as well as tactics such as next, case, and rename_i.
unhygienictacs runs tacs with name hygiene disabled.
This means that tactics that would normally create inaccessible names will instead
make regular variables. Warning: Tactics may change their variable naming
strategies at any time, so code that depends on autogenerated names is brittle.
Users should try not to use unhygienic if possible.
example:∀x:Nat,x=x:=by⊢ ∀(x:Nat),x=xunhygienicintrox:Nat⊢ x=x-- x would normally be intro'd as inaccessibleexactEq.reflxAll goals completed! 🐙-- refer to x
mvcgen will break down a Hoare triple proof goal like ⦃P⦄prog⦃Q⦄ into verification conditions,
provided that all functions used in prog have specifications registered with @[spec].
Verification Conditions and specifications
A verification condition is an entailment in the stateful logic of Std.Do.SPred
in which the original program prog no longer occurs.
Verification conditions are introduced by the mspec tactic; see the mspec tactic for what they
look like.
When there's no applicable mspec spec, mvcgen will try and rewrite an application
prog=fabc with the simp set registered via @[spec].
Features
When used like mvcgen+noLetElim[foo_spec,bar_def,instBEqFloat], mvcgen will additionally
add a Hoare triple specification foo_spec:...→⦃P⦄foo...⦃Q⦄ to spec set for a
function foo occurring in prog,
unfold a definition defbar_def...:=... in prog,
unfold any method of the instBEqFloat:BEqFloat instance in prog.
it will no longer substitute away let-expressions that occur at most once in P, Q or prog.
Config options
+noLetElim is just one config option of many. Check out Lean.Elab.Tactic.Do.VCGen.Config for all
options. Of particular note is stepLimit=some42, which is useful for bisecting bugs in
mvcgen and tracing its execution.
Extended syntax
Often, mvcgen will be used like this:
mvcgen [...]
case inv1 => by exact I1
case inv2 => by exact I2
all_goals (mleave; try grind)
There is special syntax for this:
mvcgen [...] invariants
· I1
· I2
with grind
When I1 and I2 need to refer to inaccessibles (mvcgen will introduce a lot of them for program
variables), you can use case label syntax:
This is more convenient than the equivalent ·byrename_i_acc_;exactI1acc.
Invariant suggestions
mvcgen will suggest invariants for you if you use the invariants? keyword.
mvcgen [...] invariants?
This is useful if you do not recall the exact syntax to construct invariants.
Furthermore, it will suggest a concrete invariant encoding "this holds at the start of the loop and
this must hold at the end of the loop" by looking at the corresponding VCs.
Although the suggested invariant is a good starting point, it is too strong and requires users to
interpolate it such that the inductive step can be proved. Example:
def mySum (l : List Nat) : Nat := Id.run do
let mut acc := 0
for x in l do
acc := acc + x
return acc
/--
info: Try this:
invariants
· ⇓⟨xs, letMuts⟩ => ⌜xs.prefix = [] ∧ letMuts = 0 ∨ xs.suffix = [] ∧ letMuts = l.sum⌝
-/
#guard_msgs (info) in
theorem mySum_suggest_invariant (l : List Nat) : mySum l = l.sum := by
generalize h : mySum l = r
apply Id.of_wp_run_eq h
mvcgen invariants?
all_goals admit
14.5.26.1. Tactics for Stateful Goals in Std.Do.SPred🔗
14.5.26.1.1. Starting and Stopping the Proof Mode🔗
Start the stateful proof mode of Std.Do.SPred.
This will transform a stateful goal of the form H ⊢ₛ T into ⊢ₛ H → T
upon which mintro can be used to re-introduce H and give it a name.
It is often more convenient to use mintro directly, which will
try mstart automatically if necessary.
Leaves the stateful proof mode of Std.Do.SPred, tries to eta-expand through all definitions
related to the logic of the Std.Do.SPred and gently simplifies the resulting pure Lean
proposition. This is often the right thing to do after mvcgen in order for automation to prove
the goal.
mspec is an apply-like tactic that applies a Hoare triple specification to the target of the
stateful goal.
Given a stateful goal H ⊢ₛ wp⟦prog⟧ Q', mspecfoo_spec will instantiate
foo_spec:...→⦃P⦄foo⦃Q⦄, match foo against prog and produce subgoals for
the verification conditions ?pre : H ⊢ₛ P and ?post : Q ⊢ₚ Q'.
If prog=x>>=f, then mspecSpecs.bind is tried first so that foo is matched against x
instead. Tactic mspec_no_bind does not attempt to do this decomposition.
If ?pre or ?post follow by .rfl, then they are discharged automatically.
?post is automatically simplified into constituent ⊢ₛ entailments on
success and failure continuations.
?pre and ?post.* goals introduce their stateful hypothesis under an inaccessible name.
You can give it a name with the mrename_i tactic.
Any uninstantiated MVar arising from instantiation of foo_spec becomes a new subgoal.
If the target of the stateful goal looks like funs=>_ then mspec will first mintro∀s.
If P has schematic variables that can be instantiated by doing mintro∀s, for example
foo_spec:∀(n:Nat),⦃funs=>⌜n=s⌝⦄foo⦃Q⦄, then mspec will do mintro∀s first to
instantiate n=s.
Right before applying the spec, the mframe tactic is used, which has the following effect:
Any hypothesis Hᵢ in the goal h₁:H₁, h₂:H₂, ..., hₙ:Hₙ ⊢ₛ T that is
pure (i.e., equivalent to some ⌜φᵢ⌝) will be moved into the pure context as hᵢ:φᵢ.
Additionally, mspec can be used without arguments or with a term argument:
mspec without argument will try and look up a spec for x registered with @[spec].
mspec(foo_specblah?bleh) will elaborate its argument as a term with expected type
⦃?P⦄x⦃?Q⦄ and introduce ?bleh as a subgoal.
This is useful to pass an invariant to e.g., Specs.forIn_list and leave the inductive step
as a hole.
Like intro, but introducing stateful hypotheses into the stateful context of the Std.Do.SPred
proof mode.
That is, given a stateful goal (hᵢ : Hᵢ)* ⊢ₛ P → T, mintroh transforms
into (hᵢ : Hᵢ)*, (h : P) ⊢ₛ T.
Furthermore, mintro∀s is like intros, but preserves the stateful goal.
That is, mintro∀s brings the topmost state variable s:σ in scope and transforms
(hᵢ : Hᵢ)* ⊢ₛ T (where the entailment is in Std.Do.SPred(σ::σs)) into
(hᵢ : Hᵢ s)* ⊢ₛ T s (where the entailment is in Std.Do.SPredσs).
Beyond that, mintro supports the full syntax of mcases patterns
(mintropat=(mintroh;mcaseshwithpat), and can perform multiple
introductions in sequence.
mpure_intro operates on a stateful Std.Do.SPred goal of the form P ⊢ₛ ⌜φ⌝.
It leaves the stateful proof mode (thereby discarding P), leaving the regular goal φ.
mspecialize is like specialize, but operating on a stateful Std.Do.SPred goal.
It specializes a hypothesis from the stateful context with hypotheses from either the pure
or stateful context or pure terms.
example (P Q : SPred σs) : P ⊢ₛ (P → Q) → Q := by
mintro HP HPQ
mspecialize HPQ HP
mexact HPQ
example (y : Nat) (P Q : SPred σs) (Ψ : Nat → SPred σs) (hP : ⊢ₛ P) : ⊢ₛ Q → (∀ x, P → Q → Ψ x) → Ψ (y + 1) := by
mintro HQ HΨ
mspecialize HΨ (y + 1) hP HQ
mexact HΨ
mspecialize_pure is like mspecialize, but it specializes a hypothesis from the
pure context with hypotheses from either the pure or stateful context or pure terms.
Like rcases, but operating on stateful Std.Do.SPred goals.
Example: Given a goal h : (P ∧ (Q ∨ R) ∧ (Q → R)) ⊢ₛ R,
mcaseshwith⟨-,⟨hq|hr⟩,hqr⟩ will yield two goals:
(hq : Q, hqr : Q → R) ⊢ₛ R and (hr : R) ⊢ₛ R.
That is, mcaseshwithpat has the following semantics, based on pat:
pat=□h' renames h to h' in the stateful context, regardless of whether h is pure
pat=⌜h'⌝ introduces h':φ to the pure local context if h:⌜φ⌝
(c.f. Lean.Elab.Tactic.Do.ProofMode.IsPure)
pat=h' is like pat=⌜h'⌝ if h is pure
(c.f. Lean.Elab.Tactic.Do.ProofMode.IsPure), otherwise it is like pat=□h'.
pat=_ renames h to an inaccessible name
pat=- discards h
⟨pat₁,pat₂⟩ matches on conjunctions and existential quantifiers and recurses via
pat₁ and pat₂.
⟨pat₁|pat₂⟩ matches on disjunctions, matching the left alternative via pat₁ and the right
alternative via pat₂.
mframe infers which hypotheses from the stateful context can be moved into the pure context.
This is useful because pure hypotheses "survive" the next application of modus ponens
(Std.Do.SPred.mp) and transitivity (Std.Do.SPred.entails.trans).
It is used as part of the mspec tactic.
example (P Q : SPred σs) : ⊢ₛ ⌜p⌝ ∧ Q ∧ ⌜q⌝ ∧ ⌜r⌝ ∧ P ∧ ⌜s⌝ ∧ ⌜t⌝ → Q := by
mintro _
mframe
/- `h : p ∧ q ∧ r ∧ s ∧ t` in the pure context -/
mcases h with hP
mexact h