Try doing some basic maths questions in the Lean Theorem Prover. Functions, real numbers, equivalence relations and groups. Click on README.md and then on "Open in CoCalc with one click".
License: APACHE
Mathlib tactics
In addition to the tactics found in the core library, mathlib provides a number of specific interactive tactics. Here we document the mostly commonly used ones, as well as some underdocumented tactics from core.
cc (congruence closure)
The congruence closure tactic cc tries to solve the goal by chaining equalities from context and applying congruence (ie if a = b then f a = f b). It is a finishing tactic, ie is meant to close the current goal, not to make some inconclusive progress. A mostly trivial example would be:
As an example requiring some thinking to do by hand, consider:
The tactic works by building an equality matching graph. It's a graph where the vertices are terms and they are linked by edges if they are known to be equal. Once you've added all the equalities in your context, you take the transitive closure of the graph and, for each connected component (i.e. equivalence class) you can elect a term that will represent the whole class and store proofs that the other elements are equal to it. You then take the transitive closure of these equalities under the congruence lemmas.
The cc implementation in Lean does a few more tricks: for example it derives a=b from nat.succ a = nat.succ b, and nat.succ a != nat.zero for any a.
The starting reference point is Nelson, Oppen, Fast decision procedures based on congruence closure, Journal of the ACM (1980)
The congruence lemmas for dependent type theory as used in Lean are described in Congruence closure in intensional type theory (de Moura, Selsam IJCAR 2016).
tfae
The tfae tactic suite is a set of tactics that help with proving that certain propositions are equivalent. In data/list/basic.lean there is a section devoted to propositions of the form
where p1, p2, through, pn are terms of type Prop. This proposition asserts that all the pi are pairwise equivalent. There are results that allow to extract the equivalence of two propositions pi and pj.
To prove a goal of the form tfae [p1, p2, ..., pn], there are two tactics. The first tactic is tfae_have. As an argument it takes an expression of the form i arrow j, where i and j are two positive natural numbers, and arrow is an arrow such as →, ->, ←, <-, ↔, or <->. The tactic tfae_have : i arrow j sets up a subgoal in which the user has to prove the equivalence (or implication) of pi and pj.
The remaining tactic, tfae_finish, is a finishing tactic. It collects all implications and equivalences from the local context and computes their transitive closure to close the main goal.
tfae_have and tfae_finish can be used together in a proof as follows:
rcases
The rcases tactic is the same as cases, but with more flexibility in the with pattern syntax to allow for recursive case splitting. The pattern syntax uses the following recursive grammar:
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, p x, 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.
rcases h : e with PAT will do the same as rcases e with PAT with the exception that an assumption h : e = PAT will be added to the context.
rcases? e will perform case splits on e in the same way as rcases e, but rather than accepting a pattern, it does a maximal cases and prints the pattern that would produce this case splitting. The default maximum depth is 5, but this can be modified with rcases? e : n.
rintro
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, rintros (a | ⟨b, c⟩) ⟨d, e⟩ will introduce two variables, and then do case splits on both of them producing two subgoals, one with variables a d e and the other with b c d e.
rintro? will introduce and case split on variables in the same way as rintro, but will also print the rintro invocation that would have the same result. Like rcases?, rintro? : n allows for modifying the depth of splitting; the default is 5.
obtain
The obtain tactic is a combination of have and rcases.
is equivalent to
The syntax obtain ⟨patt⟩ : type := proof is also supported.
If ⟨patt⟩ is omitted, rcases will try to infer the pattern.
If type is omitted, := proof is required.
simpa
This is a "finishing" tactic modification of simp. It has two forms.
simpa [rules, ...] using ewill simplify the goal and the type ofeusingrules, then try to close the goal usinge.Simplifying the type of
emakes 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.simpa [rules, ...]will simplify the goal and the type of a hypothesisthisif present in the context, then try to close the goal using theassumptiontactic.
replace
Acts like have, but removes a hypothesis with the same name as this one. For example if the state is h : p ⊢ goal and f : p → q, then after replace h := f h the goal will be h : q ⊢ goal, where have h := f h would result in the state h : p, h : q ⊢ goal. This can be used to simulate the specialize and apply at tactics of Coq.
elide/unelide
The elide n (at ...) tactic hides all subterms of the target goal or hypotheses beyond depth n by replacing them with hidden, which is a variant on the identity function. (Tactics should still mostly be able to see through the abbreviation, but if you want to unhide the term you can use unelide.)
The unelide (at ...) tactic removes all hidden subterms in the target types (usually added by elide).
finish/clarify/safe
These tactics do straightforward things: they call the simplifier, split conjunctive assumptions, eliminate existential quantifiers on the left, and look for contradictions. They rely on ematching and congruence closure to try to finish off a goal at the end.
The procedures do split on disjunctions and recreate the smt state for each terminal call, so they are only meant to be used on small, straightforward problems.
finish: solves the goal or fails
clarify: makes as much progress as possible while not leaving more than one goal
safe: splits freely, finishes off whatever subgoals it can, and leaves the rest
All accept an optional list of simplifier rules, typically definitions that should be expanded. (The equations and identities should not refer to the local context.) All also accept an optional list of ematch lemmas, which must be preceded by using.
abel
Evaluate expressions in the language of additive, commutative monoids and groups. It attempts to prove the goal outright if there is no at specifier and the target is an equality, but if this fails, it falls back to rewriting all monoid expressions into a normal form. If there is an at specifier, it rewrites the given target into a normal form.
norm_num
Normalises numerical expressions. It supports the operations + - * / ^ and % over numerical types such as ℕ, ℤ, ℚ, ℝ, ℂ, and can prove goals of the form A = B, A ≠ B, A < B and A ≤ B, where A and B are numerical expressions. It also has a relatively simple primality prover.
ring
Evaluate expressions in the language of commutative (semi)rings. Based on Proving Equalities in a Commutative Ring Done Right in Coq by Benjamin Grégoire and Assia Mahboubi.
The variant ring! uses a more aggessive reducibility setting to determine equality of atoms.
ring_exp
Evaluate expressions in commutative (semi)rings, allowing for variables in the exponent.
This tactic extends ring: it should solve every goal that ring can solve. Additionally, it knows how to evaluate expressions with complicated exponents (where ring only understands constant exponents). The variants ring_exp! and ring_exp_eq! use a more aggessive reducibility setting to determine equality of atoms.
For example:
field_simp
The goal of field_simp is to reduce an expression in a field to an expression of the form n / d where neither n nor d contains any division symbol, just using the simplifier (with a carefully crafted simpset named field_simps) to reduce the number of division symbols whenever possible by iterating the following steps:
write an inverse as a division
in any product, move the division to the right
if there are several divisions in a product, group them together at the end and write them as a single division
reduce a sum to a common denominator
If the goal is an equality, this simpset will also clear the denominators, so that the proof can normally be concluded by an application of ring or ring_exp.
field_simp [hx, hy] is a short form for simp [-one_div_eq_inv, hx, hy] with field_simps
Note that this naive algorithm will not try to detect common factors in denominators to reduce the complexity of the resulting expression. Instead, it relies on the ability of ring to handle complicated expressions in the next step.
As always with the simplifier, reduction steps will only be applied if the preconditions of the lemmas can be checked. This means that proofs that denominators are nonzero should be included. The fact that a product is nonzero when all factors are, and that a power of a nonzero number is nonzero, are included in the simpset, but more complicated assertions (especially dealing with sums) should be given explicitly. If your expression is not completely reduced by the simplifier invocation, check the denominators of the resulting expression and provide proofs that they are nonzero to enable further progress.
The invocation of field_simp removes the lemma one_div_eq_inv (which is marked as a simp lemma in core) from the simpset, as this lemma works against the algorithm explained above.
For example,
congr'
Same as the congr tactic, but takes an optional argument which gives the depth of 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 congr' 2 produces the intended ⊢ x + y = y + x. If, at any point, a subgoal matches a hypothesis then the subgoal will be closed.
convert
The exact e and refine e tactics require a term e whose type is definitionally equal to the goal. convert e is similar to refine e, but the type of e is not required to exactly match the goal. Instead, new goals are created for differences between the type of e and the goal. For example, in the proof state
the tactic convert e will change the goal to
In this example, the new goal can be solved using ring.
The syntax convert ← e will reverse the direction of the new goals (producing ⊢ 2 * n = n + n in this example).
Internally, convert e works by creating a new goal asserting that the goal equals the type of e, then simplifying it using congr'. The syntax convert e using n can be used to control the depth of matching (like congr' n). In the example, convert e using 1 would produce a new goal ⊢ n + n + 1 = 2 * n + 1.
unfold_coes
Unfold coercion-related definitions
Instance cache tactics
For performance reasons, Lean does not automatically update its database of class instances during a proof. The group of tactics described below helps forcing such updates. For a simple (but very artificial) example, consider the function default from the core library. It has type Π (α : Sort u) [inhabited α], α, so one can use default α only if Lean can find a registered instance of inhabited α. Because the database of such instance is not automatically updated during a proof, the following attempt won't work (Lean will not pick up the instance from the local context):
However, it will work, producing the identity function, if one replaces have by its variant haveI described below.
resetI: Reset the instance cache. This allows any instances currently in the context to be used in typeclass inference.unfreezeI: Unfreeze local instances, which allows us to revert instances in the contextintroI/introsI:intro/introsfollowed byresetI. Likeintro/intros, but uses the introduced variable in typeclass inference.haveI/letI:have/letfollowed byresetI. Used to add typeclasses to the context so that they can be used in typeclass inference. The syntaxhaveI := <proof>andhaveI : t := <proof>is supported, buthaveI : t, from _andhaveI : t, { <proof> }are not; in these cases usehave : t, { <proof> }, resetIdirectly).exactI:resetIfollowed byexact. Likeexact, but uses all variables in the context for typeclass inference.
suggest
suggest lists possible usages of the refine tactic and leaves the tactic state unchanged. It is intended as a complement of the search function in your editor, the #find tactic, and library_search.
suggest takes an optional natural number num as input and returns the first num (or less, if all possibilities are exhausted) possibilities ordered by length of lemma names. The default for num is 50.
For performance reasons suggest uses monadic lazy lists (mllist). This means that suggest might miss some results if num is not large enough. However, because suggest uses monadic lazy lists, smaller values of num run faster than larger values.
An example of suggest in action,
prints the list,
library_search
library_search is a tactic to identify existing lemmas in the library. It tries to close the current goal by applying a lemma from the library, then discharging any new goals using solve_by_elim.
Typical usage is:
library_search prints a trace message showing the proof it found, shown above as a comment. Typically you will then copy and paste this proof, replacing the call to library_search.
solve_by_elim
The tactic solve_by_elim repeatedly applies assumptions to the current goal, and succeeds if this eventually discharges the main goal.
also attempts to discharge the goal using congruence closure before each round of applying assumptions.
solve_by_elim* tries to solve all goals together, using backtracking if a solution for one goal makes other goals impossible.
By default solve_by_elim also applies congr_fun and congr_arg against the goal.
The assumptions can be modified with similar syntax as for simp:
solve_by_elim [h₁, h₂, ..., hᵣ]also applies the named lemmas (or all lemmas tagged with the named attributes).solve_by_elim only [h₁, h₂, ..., hᵣ]does not include the local context,congr_fun, orcongr_argunless they are explicitly included.solve_by_elim [-id_1, ... -id_n]uses the default assumptions, removing the specified ones.
ext1 / ext
ext1 idselects and apply one extensionality lemma (with attributeext), usingid, if provided, to name a local constant introduced by the lemma. Ifidis omitted, the local constant is named automatically, as perintro.extapplies as many extensionality lemmas as possible;ext ids, withidsa list of identifiers, finds extentionality and applies them until it runs out of identifiers inidsto name the local constants.
When trying to prove:
applying ext x y yields:
by applying functional extensionality and set extensionality.
A maximum depth can be provided with ext x y z : 3.
The ext attribute
Tag lemmas of the form:
The attribute indexes extensionality lemma using the type of the objects (i.e. my_collection) which it gets from the statement of the lemma. In some cases, the same lemma can be used to state the extensionality of multiple types that are definitionally equivalent.
Those parameters are cumulative. The following are equivalent:
and
One removes type names from the list for one lemma with:
Also, the following:
is equivalent to
The * parameter indicates to simply infer the type from the lemma's statement.
This allows us specify type synonyms along with the type that referred to in the lemma statement.
Attribute ext can be applied to a structure to generate its extensionality lemma:
will generate:
refine_struct
refine_struct { .. } acts like refine but works only with structure instance literals. It creates a goal for each missing field and tags it with the name of the field so that have_field can be used to generically refer to the field currently being refined.
As an example, we can use refine_struct to automate the construction semigroup instances:
have_field, used after refine_struct _ poses field as a local constant with the type of the field of the current goal:
behaves like
apply_rules
apply_rules hs n applies the list of lemmas hs and assumption on the first goal and the resulting subgoals, iteratively, at most n times. n is optional, equal to 50 by default. hs can contain user attributes: in this case all theorems with this attribute are added to the list of rules.
For instance:
h_generalize
h_generalize Hx : e == x matches on cast _ e in the goal and replaces it with x. It also adds Hx : e == x as an assumption. If cast _ e appears multiple times (not necessarily with the same proof), they are all replaced by x. cast eq.mp, eq.mpr, eq.subst, eq.substr, eq.rec and eq.rec_on are all treated as casts.
h_generalize Hx : e == x with hadds hypothesisα = βwithe : α, x : β;h_generalize Hx : e == x with _chooses automatically chooses the name of assumptionα = β;h_generalize! Hx : e == xrevertsHx;when
Hxis omitted, assumptionHx : e == xis not added.
pi_instance
pi_instance constructs an instance of my_class (Π i : I, f i) where we know Π i, my_class (f i). If an order relation is required, it defaults to pi.partial_order. Any field of the instance that pi_instance cannot construct is left untouched and generated as a new goal.
assoc_rewrite
assoc_rewrite [h₀, ← h₁] at ⊢ h₂ behaves like rewrite [h₀, ← h₁] at ⊢ h₂ with the exception that associativity is used implicitly to make rewriting possible.
restate_axiom
restate_axiom makes a new copy of a structure field, first definitionally simplifying the type. This is useful to remove auto_param or opt_param from the statement.
As an example, we have:
By default, restate_axiom names the new lemma by removing a trailing ', or otherwise appending _lemma if there is no trailing '. You can also give restate_axiom a second argument to specify the new name, as in
def_replacer
def_replacer foo sets up a stub definition foo : tactic unit, which can effectively be defined and re-defined later, by tagging definitions with @[foo].
@[foo] meta def foo_1 : tactic unit := ...replaces the current definition offoo.@[foo] meta def foo_2 (old : tactic unit) : tactic unit := ...replaces the current definition offoo, and provides access to the previous definition viaold. (The argument can also be anoption (tactic unit), which is provided asnoneif this is the first definition tagged with@[foo]sincedef_replacerwas invoked.)
def_replacer foo : α → β → tactic γ allows the specification of a replacer with custom input and output types. In this case all subsequent redefinitions must have the same type, or the type α → β → tactic γ → tactic γ or α → β → option (tactic γ) → tactic γ analogously to the previous cases.
tidy
tidy attempts to use a variety of conservative tactics to solve the goals. In particular, tidy uses the chain tactic to repeatedly apply a list of tactics to the goal and recursively on new goals, until no tactic makes further progress.
tidy can report the tactic script it found using tidy?. As an example
The default list of tactics can be found by looking up the definition of default_tidy_tactics.
This list can be overriden using tidy { tactics := ... }. (The list must be a list of tactic string, so that tidy? can report a usable tactic script.)
linarith
linarith attempts to find a contradiction between hypotheses that are linear (in)equalities. Equivalently, it can prove a linear inequality by assuming its negation and proving false.
In theory, linarith should prove any goal that is true in the theory of linear arithmetic over the rationals. While there is some special handling for non-dense orders like nat and int, this tactic is not complete for these theories and will not prove every true goal.
An example:
linarith will use all appropriate hypotheses and the negation of the goal, if applicable.
linarith [t1, t2, t3] will additionally use proof terms t1, t2, t3.
linarith only [h1, h2, h3, t1, t2, t3] will use only the goal (if relevant), local hypotheses h1, h2, h3, and proofs t1, t2, t3. It will ignore the rest of the local context.
linarith! will use a stronger reducibility setting to try to identify atoms. For example,
will fail, because linarith will not identify x and id x. linarith! will. This can sometimes be expensive.
linarith {discharger := tac, restrict_type := tp, exfalso := ff} takes a config object with three optional arguments.
dischargerspecifies a tactic to be used for reducing an algebraic equation in the proof stage. The default isring. Other options currently includering SOPorsimpfor basic problems.restrict_typewill only use hypotheses that are inequalities overtp. This is useful if you have e.g. both integer and rational valued inequalities in the local context, which can sometimes confuse the tactic.If
exfalsois false,linarithwill fail when the goal is neither an inequality norfalse. (True by default.)
choose
choose a b h using hyp takes an hypothesis hyp of the form ∀ (x : X) (y : Y), ∃ (a : A) (b : B), P x y a b for some P : X → Y → A → B → Prop and outputs into context a function a : X → Y → A, b : X → Y → B and a proposition h stating ∀ (x : X) (y : Y), P x y (a x y) (b x y). It presumably also works with dependent versions.
Example:
squeeze_simp / squeeze_simpa
squeeze_simp and squeeze_simpa perform the same task with the difference that squeeze_simp relates to simp while squeeze_simpa relates to simpa. The following applies to both squeeze_simp and squeeze_simpa.
squeeze_simp behaves like simp (including all its arguments) and prints a simp only invokation to skip the search through the simp lemma list.
For instance, the following is easily solved with simp:
To guide the proof search and speed it up, we may replace simp with squeeze_simp:
squeeze_simp suggests a replacement which we can use instead of squeeze_simp.
squeeze_simp only prints nothing as it already skips the simp list.
This tactic is useful for speeding up the compilation of a complete file. Steps:
search and replace
simpwithsqueeze_simp(the space helps avoid the replacement ofsimpin@[simp]) throughout the file.Starting at the beginning of the file, go to each printout in turn, copy the suggestion in place of
squeeze_simp.after all the suggestions were applied, search and replace
squeeze_simpwithsimpto remove the occurrences ofsqueeze_simpthat did not produce a suggestion.
Known limitation(s):
in cases where
squeeze_simpis used after a;(e.g.cases x; squeeze_simp),squeeze_simpwill produce as many suggestions as the number of goals it is applied to. It is likely that none of the suggestion is a good replacement but they can all be combined by concatenating their list of lemmas.
fin_cases
fin_cases h performs case analysis on a hypothesis of the form
h : A, where[fintype A]is available, orh ∈ A, whereA : finset X,A : multiset XorA : list X.
fin_cases * performs case analysis on all suitable hypotheses.
As an example, in
after fin_cases p; simp, there are three goals, f 0, f 1, and f 2.
conv
The conv tactic is built-in to lean. Inside conv blocks mathlib currently additionally provides
erw,ringandring2,norm_num, andconv(within anotherconv). Usingconvinside aconvblock allows the user to return to the previous state of the outerconvblock after it is finished. Thus you can continue editing an expression without having to start a newconvblock and re-scoping everything. For example:
Without conv the above example would need to be proved using two successive conv blocks each beginning with to_lhs.
Also, as a shorthand conv_lhs and conv_rhs are provided, so that
just means
and likewise for to_rhs.
mono
monoapplies a monotonicity rule.mono*applies monotonicity rules repetitively.mono with x ≤ yormono with [0 ≤ x,0 ≤ y]creates an assertion for the listed propositions. Those help to select the right monotonicity rule.mono leftormono rightis useful when proving strict orderings: forx + y < w + zcould be broken down into eitherleft:
x ≤ wandy < zorright:
x < wandy ≤ z
To use it, first import tactic.monotonicity.
Here is an example of mono:
More succinctly, we can prove the same goal as:
ac_mono
ac_mono reduces the f x ⊑ f y, for some relation ⊑ and a monotonic function f to x ≺ y.
ac_mono* unwraps monotonic functions until it can't.
ac_mono^k, for some literal number k applies monotonicity k times.
ac_mono h, with h a hypothesis, unwraps monotonic functions and uses h to solve the remaining goal. Can be combined with * or ^k: ac_mono* h
ac_mono : p asserts p and uses it to discharge the goal result unwrapping a series of monotonic functions. Can be combined with * or ^k: ac_mono* : p
In the case where f is an associative or commutative operator, ac_mono will consider any possible permutation of its arguments and use the one the minimizes the difference between the left-hand side and the right-hand side.
To use it, first import tactic.monotonicity.
ac_mono can be used as follows:
As with mono*, ac_mono* solves the goal in one go and so does ac_mono* h₁. The latter syntax becomes especially interesting in the following example:
By giving ac_mono the assumption h₁, we are asking ac_refl to stop earlier than it would normally would.
use
Similar to existsi. use x will instantiate the first term of an ∃ or Σ goal with x. It will then try to close the new goal using triv, or try to simplify it by applying exists_prop. Unlike existsi, x is elaborated with respect to the expected type.
use will alternatively take a list of terms [x0, ..., xn].
use will work with constructors of arbitrary inductive types.
Examples:
clear_aux_decl
clear_aux_decl clears every aux_decl in the local context for the current goal. This includes the induction hypothesis when using the equation compiler and _let_match and _fun_match.
It is useful when using a tactic such as finish, simp * or subst that may use these auxiliary declarations, and produce an error saying the recursion is not well founded.
set
set a := t with h is a variant of let a := t. It adds the hypothesis h : a = t to the local context and replaces t with a everywhere it can.
set a := t with ←h will add h : t = a instead.
set! a := t with h does not do any replacing.
omega
omega attempts to discharge goals in the quantifier-free fragment of linear integer and natural number arithmetic using the Omega test. In other words, the core procedure of omega works with goals of the form
where x₁, ... xₖ are integer (resp. natural number) variables, and P is a quantifier-free formula of linear integer (resp. natural number) arithmetic. For instance:
By default, omega tries to guess the correct domain by looking at the goal and hypotheses, and then reverts all relevant hypotheses and variables (e.g., all variables of type nat and Props in linear natural number arithmetic, if the domain was determined to be nat) to universally close the goal before calling the main procedure. Therefore, omega will often work even if the goal is not in the above form:
But this behaviour is not always optimal, since it may revert irrelevant hypotheses or incorrectly guess the domain. Use omega manual to disable automatic reverts, and omega int or omega nat to specify the domain.
omega handles nat subtraction by repeatedly rewriting goals of the form P[t-s] into P[x] ∧ (t = s + x ∨ (t ≤ s ∧ x = 0)), where x is fresh. This means that each (distinct) occurrence of subtraction will cause the goal size to double during DNF transformation.
omega implements the real shadow step of the Omega test, but not the dark and gray shadows. Therefore, it should (in principle) succeed whenever the negation of the goal has no real solution, but it may fail if a real solution exists, even if there is no integer/natural number solution.
push_neg
This tactic pushes negations inside expressions. For instance, given an assumption
writing push_neg at h will turn h into
(the pretty printer does not use the abreviations ∀ δ > 0 and ∃ ε > 0 but this issue has nothing to do with push_neg). Note that names are conserved by this tactic, contrary to what would happen with simp using the relevant lemmas. One can also use this tactic at the goal using push_neg, at every assumption and the goal using push_neg at * or at selected assumptions and the goal using say push_neg at h h' ⊢ as usual.
contrapose
Transforms the goal into its contrapositive.
contrapose turns a goal P → Q into ¬ Q → ¬ P
contrapose! turns a goal P → Q into ¬ Q → ¬ P and pushes negations inside P and Q using push_neg
contrapose h first reverts the local assumption h, and then uses contrapose and intro h
contrapose! h first reverts the local assumption h, and then uses contrapose! and intro h
contrapose h with new_h uses the name new_h for the introduced hypothesis
tautology
This tactic (with shorthand tauto) breaks down assumptions of the form _ ∧ _, _ ∨ _, _ ↔ _ and ∃ _, _ and splits a goal of the form _ ∧ _, _ ↔ _ or ∃ _, _ until it can be discharged using reflexivity or solve_by_elim. This is a finishing tactic: it either closes the goal of raises an error.
The variants tautology! or tauto! use the law of excluded middle.
For instance, one can write:
and the decidability assumptions can be dropped if tauto! is used instead of tauto.
norm_cast
This tactic normalizes casts inside expressions. It is basically a simp tactic with a specific set of lemmas to move casts upwards in the expression. Therefore it can be used more safely as a non-terminating tactic. It also has special handling of numerals.
For instance, given an assumption
writing norm_cast at h will turn h into
You can also use exact_mod_cast, apply_mod_cast, rw_mod_cast or assumption_mod_cast. Writing exact_mod_cast h and apply_mod_cast h will normalize the goal and h before using exact h or apply h. Writing assumption_mod_cast will normalize the goal and for every expression h in the context it will try to normalize h and use exact h. rw_mod_cast acts like the rw tactic but it applies norm_cast between steps.
These tactics work with three attributes, elim_cast, move_cast and squash_cast.
elim_cast is for elimination lemmas of the shape Π ..., P ↑a1 ... ↑an = P a1 ... an, for instance:
move_cast is for compositional lemmas of the shape Π ..., ↑(P a1 ... an) = P ↑a1 ... ↑an, for instance:
squash_cast is for lemmas of the shape Π ..., ↑↑a = ↑a, for instance:
push_cast rewrites the expression to move casts toward the leaf nodes. This uses move_cast lemmas in the "forward" direction. For example, ↑(a + b) will be written to ↑a + ↑b. It is equivalent to simp only with push_cast, and can also be used at hypotheses with push_cast at h.
convert_to
convert_to g using n attempts to change the current goal to g, but unlike change, it will generate equality proof obligations using congr' n to resolve discrepancies. convert_to g defaults to using congr' 1.
ac_change is convert_to followed by ac_refl. It is useful for rearranging/reassociating e.g. sums:
apply_fun
Apply a function to some local assumptions which are either equalities or inequalities. For instance, if the context contains h : a = b and some function f then apply_fun f at h turns h into h : f a = f b. When the assumption is an inequality h : a ≤ b, a side goal monotone f is created, unless this condition is provided using apply_fun f at h using P where P : monotone f, or the mono tactic can prove it.
Typical usage is:
swap
swap n will move the nth goal to the front. swap defaults to swap 2, and so interchanges the first and second goals.
rotate
rotate moves the first goal to the back. rotate n will do this n times.
The reassoc attribute
The reassoc attribute can be applied to a lemma
and produce
The name of the produced lemma can be specified with @[reassoc other_lemma_name]. If simp is added first, the generated lemma will also have the simp attribute.
The reassoc_of function
reassoc_of h takes local assumption h and add a ≫ f term on the right of both sides of the equality. Instead of creating a new assumption from the result, reassoc_of h stands for the proof of that reassociated statement. This prevents poluting the local context with complicated assumptions used only once or twice.
In the following, assumption h is needed in a reassociated form. Instead of proving it as a new goal and adding it as an assumption, we use reassoc_of h as a rewrite rule which works just as well.
Although reassoc_of is not a tactic or a meta program, its type is generated through meta-programming to make it usable inside normal expressions.
lift
Lift an expression to another type.
Usage:
'lift' expr 'to' expr ('using' expr)? ('with' id (id id?)?)?.If
n : ℤandhn : n ≥ 0then the tacticlift n to ℕ using hncreates a new constant of typeℕ, also namednand replaces all occurrences of the old variable(n : ℤ)with↑n(wherenin the new variable). It will removenandhnfrom the context.So for example the tactic
lift n to ℕ using hntransforms the goaln : ℤ, hn : n ≥ 0, h : P n ⊢ n = 3ton : ℕ, h : P ↑n ⊢ ↑n = 3(herePis some term of typeℤ → Prop).
The argument
using hnis optional, the tacticlift n to ℕdoes the same, but also creates a new subgoal thatn ≥ 0(wherenis the old variable).So for example the tactic
lift n to ℕtransforms the goaln : ℤ, h : P n ⊢ n = 3to two goalsn : ℕ, h : P ↑n ⊢ ↑n = 3andn : ℤ, h : P n ⊢ n ≥ 0.
You can also use
lift n to ℕ using ewhereeis any expression of typen ≥ 0.Use
lift n to ℕ with kto specify the name of the new variable.Use
lift n to ℕ with k hkto also specify the name of the equality↑k = n. In this case,nwill remain in the context. You can userflfor the name ofhkto substitutenaway (i.e. the default behavior).You can also use
lift e to ℕ with k hkwhereeis any expression of typeℤ. In this case, thehkwill always stay in the context, but it will be used to rewriteein all hypotheses and the target.So for example the tactic
lift n + 3 to ℕ using hn with k hktransforms the goaln : ℤ, hn : n + 3 ≥ 0, h : P (n + 3) ⊢ n + 3 = 2 * nto the goaln : ℤ, k : ℕ, hk : ↑k = n + 3, h : P ↑k ⊢ ↑k = 2 * n.
The tactic
lift n to ℕ using hwill removehfrom the context. If you want to keep it, specify it again as the third argument towith, like this:lift n to ℕ using h with n rfl h.More generally, this can lift an expression from
αtoβassuming that there is an instance ofcan_lift α β. In this case the proof obligation is specified bycan_lift.cond.Given an instance
can_lift β γ, it can also liftα → βtoα → γ; more generally, givenβ : Π a : α, Type*,γ : Π a : α, Type*, and[Π a : α, can_lift (β a) (γ a)], it automatically generates an instancecan_lift (Π a, β a) (Π a, γ a).
import_private
import_private foo from bar finds a private declaration foo in the same file as bar and creates a local notation to refer to it.
import_private foo, looks for foo in all (imported) files.
When possible, make foo non-private rather than using this feature.
default_dec_tac'
default_dec_tac' is a replacement for the core tactic default_dec_tac, fixing a bug. This bug is often indicated by a message nested exception message: tactic failed, there are no goals to be solved,and solved by appending using_well_founded wf_tacs to the recursive definition. See also additional documentation of using_well_founded in docs/extras/well_founded_recursion.md.
simps
The
@[simps]attribute automatically derives lemmas specifying the projections of the declaration.Example: (note that the forward and reverse functions are specified differently!)
derives two simp-lemmas:
It does not derive simp-lemmas for the prop-valued projections.
It will automatically reduce newly created beta-redexes, but not unfold any definitions.
If one of the fields itself is a structure, this command will recursively create simp-lemmas for all fields in that structure.
You can use
@[simps proj1 proj2 ...]to only generate the projection lemmas for the specified projections. For example:If one of the values is an eta-expanded structure, we will eta-reduce this structure.
You can use
@[simps lemmas_only]to derive the lemmas, but not mark them as simp-lemmas.You can use
@[simps short_name]to only use the name of the last projection for the name of the generated lemmas.The precise syntax is
('simps' 'lemmas_only'? 'short_name'? ident*).If one of the projections is marked as a coercion, the generated lemmas do not use this coercion.
@[simps]reduces let-expressions where necessary.If one of the fields is a partially applied constructor, we will eta-expand it (this likely never happens).
clear'
An improved version of the standard clear tactic. clear is sensitive to the order of its arguments: clear x y may fail even though both x and y could be cleared (if the type of y depends on x). clear' lifts this limitation.
clear_dependent
A variant of clear' which clears not only the given hypotheses, but also any other hypotheses depending on them.
simp_rw
simp_rw functions as a mix of simp and rw. Like rw, it applies each rewrite rule in the given order, but like simp it repeatedly applies these rules and also under binders like ∀ x, ..., ∃ x, ... and λ x, ....
Usage:
simp_rw [lemma_1, ..., lemma_n]will rewrite the goal by applying the lemmas in that order.simp_rw [lemma_1, ..., lemma_n] at h₁ ... hₙwill rewrite the given hypotheses.simp_rw [...] at ⊢ h₁ ... hₙrewrites the goal as well as the given hypotheses.simp_rw [...] at *rewrites in the whole context: all hypotheses and the goal.
For example, neither simp nor rw can solve the following, but simp_rw can:
Lemmas passed to simp_rw must be expressions that are valid arguments to simp.