Lean 语言参考

14.5. 策略参考🔗

14.5.1. 古典逻辑🔗

🔗tactic
classical

classical tacs runs tacs in a scope where Classical.propDecidable is a low priority local instance.

Note that classical is a scoping tactic: it adds the instance only within the scope of the tactic.

14.5.2. 假设🔗

🔗tactic
assumption

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 show t by assumption.

🔗tactic
apply_assumption

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_assumption only [...]. You can use apply_assumption [-h] to omit a local hypothesis. You can use apply_assumption using [a₁, ...] to use all lemmas which have been labelled with the attributes aᵢ (these attributes must be created using register_label_attr).

apply_assumption will use consequences of local hypotheses obtained via symm.

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).

14.5.3. 量词🔗

🔗tactic
exists

exists e₁, e₂, ... is shorthand for refine e₁, e₂, ...; try trivial. It is useful for existential goals.

🔗tactic
intro

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 _.

  • intro x y 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 : β ⊢ ...
  • intro rfl is short for intro h; subst h, 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
    | ...
    
🔗tactic
intros

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.

intros x y z is equivalent to intro x y z 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.

  • repeat intro 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 (p q : Prop) : p q p := p:Propq:Propp q p p:Propq:Propa✝¹:pa✝:qp /- Tactic state a✝¹ : p a✝ : q ⊢ p -/ All goals completed! 🐙

Let-bindings:

example : let n := 1; let k := 2; n + k = 3 := let n := 1; let k := 2; n + k = 3 n✝:Nat := 1k✝:Nat := 2n✝ + k✝ = 3 /- n✝ : Nat := 1 k✝ : Nat := 2 ⊢ n✝ + k✝ = 3 -/ All goals completed! 🐙

Does not unfold definitions:

def AllEven (f : Nat Nat) := n, f n % 2 = 0 declaration uses `sorry`example : (f : Nat Nat), AllEven f AllEven (fun k => f (k + 1)) := (f : Nat Nat), AllEven f AllEven fun k => f (k + 1) f✝:Nat Nata✝:AllEven f✝AllEven fun k => f✝ (k + 1) /- Tactic state f✝ : Nat → Nat a✝ : AllEven f✝ ⊢ AllEven fun k => f✝ (k + 1) -/ All goals completed! 🐙
🔗tactic
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, 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 a d e and the other with b c d e.

rintro, unlike rcases, also supports the form (x y : ty) for introducing and type-ascripting multiple variables at once, similar to binders.

14.5.4. 关系🔗

🔗tactic
rfl

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].

🔗tactic
rfl'

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).

🔗tactic
apply_rfl

The same as rfl, but without trying eq_refl at the end.

attributeReflexive Relations

refl 属性将引理标记为某些关系的自反性证明。 这些引理由 rflrfl'apply_rfl策略使用。

attr ::= ...
    | refl
🔗tactic
symm
  • 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.

  • symm at h will rewrite a hypothesis h : t ~ u to h : u ~ t.

🔗tactic
symm_saturate

For every hypothesis h : a ~ b where a @[symm] lemma is available, add a hypothesis h_symm : b ~ a.

attributeSymmetric Relations

symm 属性将引理标记为关系对称的证明。 这些引理由 symmsymm_saturate策略使用。

attr ::= ...
    | symm
🔗tactic
calc

Step-wise reasoning over transitive relations.

calc
  a = b := pab
  b = c := pbc
  ...
  y = z := pyz

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:

calc abc
  _ = bce := pabce
  _ = cef := pbcef
  ...
  _ = xyz := pwxyz

calc works as a term, as a tactic or as a conv tactic.

See Theorem Proving in Lean 4 for more information.

🔗type class
Trans.{u, v, w, u_1, u_2, u_3} {α : Sort u_1} {β : Sort u_2} {γ : Sort u_3} (r : α β Sort u) (s : β γ Sort v) (t : outParam (α γ Sort w)) : Sort (max (max (max (max (max (max 1 u) u_1) u_2) u_3) v) w)
Trans.{u, v, w, u_1, u_2, u_3} {α : Sort u_1} {β : Sort u_2} {γ : Sort u_3} (r : α β Sort u) (s : β γ Sort v) (t : outParam (α γ Sort w)) : Sort (max (max (max (max (max (max 1 u) u_1) u_2) u_3) v) w)

Transitive chaining of proofs, used e.g. by calc.

It takes two relations r and s as "input", and produces an "output" relation t, with the property that r a b and s b c implies t a c. The calc tactic uses this so that when it sees a chain with a b and b < c it knows that this should be a proof of a < c because there is an instance Trans (··) (·<·) (·<·).

Instance Constructor

Trans.mk.{u, v, w, u_1, u_2, u_3}

Methods

trans : {a : α}  {b : β}  {c : γ}  r a b  s b c  t a c

Compose two proofs by transitivity, generalized over the relations involved.

14.5.4.1. 平等🔗

🔗tactic
subst

subst x... substitutes each hypothesis x with a definition found in the local context, then eliminates the hypothesis.

  • If x is a local definition, then its definition is used.

  • Otherwise, if there is a hypothesis of the form x = e or e = x, then e is used for the definition of x.

If h : a = b, then subst h may be used if either a or b unfolds to a local hypothesis. This is similar to the cases h tactic.

See also: subst_vars for substituting all local hypotheses that have a defining equation.

🔗tactic
subst_eqs

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.

🔗tactic
subst_vars

Applies subst to all hypotheses of the form h : x = t or h : t = x.

🔗tactic
congr

Apply congruence (recursively) to goals of the form f as = f bs and f as f bs. 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 congr 2 produces the intended x + y = y + x.

🔗tactic
eq_refl

eq_refl is equivalent to exact rfl, but has a few optimizations.

🔗tactic
ac_rfl

ac_rfl proves equalities up to application of an associative and commutative operator.

instance : Std.Associative (α := Nat) (.+.) := Nat.add_assoc instance : Std.Commutative (α := Nat) (.+.) := Nat.add_comm example (a b c d : Nat) : a + b + c + d = d + (b + c) + a := a:Natb:Natc:Natd:Nata + b + c + d = d + (b + c) + a All goals completed! 🐙

14.5.5. 结合性和交换性🔗

🔗tactic
ac_nf

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_nf at l 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.

instance : Std.Associative (α := Nat) (.+.) := Nat.add_assoc instance : Std.Commutative (α := Nat) (.+.) := Nat.add_comm example (a b c d : Nat) : a + b + c + d = d + (b + c) + a := a:Natb:Natc:Natd:Nata + b + c + d = d + (b + c) + a All goals completed! 🐙 -- goal: a + (b + (c + d)) = a + (b + (c + d))
🔗tactic
ac_nf0

Implementation of ac_nf (the full ac_nf calls trivial afterwards).

14.5.6. 引理🔗

🔗tactic
exact

exact e closes the main goal if its target type matches that of e.

🔗tactic
apply

apply e 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.

🔗tactic
refine

refine e behaves like exact e, 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.

🔗tactic
refine'

refine' e behaves like refine e, except that unsolved placeholders (_) and implicit parameters are also converted into new goals.

🔗tactic
solve_by_elim

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 discharges the current goal or fails.

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_elim only [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_elim using [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).

  • exfalso: allow calling exfalso and trying again if solve_by_elim fails (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.

🔗tactic
apply_rules

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_rules only [...].

You can use apply_rules using [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.

🔗tactic
as_aux_lemma

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.

14.5.7. 谬误🔗

🔗tactic
exfalso

exfalso converts a goal tgt into False by applying False.elim.

🔗tactic
contradiction

contradiction closes the main goal if its hypotheses are "trivially contradictory".

  • Inductive type/family with no applicable constructors

    example (h : False) : p := p:Sort ?u.3h:Falsep All goals completed! 🐙
  • Injectivity of constructors

    example (h : none = some true) : p := p:Sort ?u.6h:none = some truep All goals completed! 🐙 --
  • Decidable false proposition

    example (h : 2 + 2 = 3) : p := p:Sort ?u.13h:2 + 2 = 3p All goals completed! 🐙
  • Contradictory hypotheses

    example (h : p) (h' : ¬ p) : q := p:Propq:Sort ?u.5h:ph':¬pq All goals completed! 🐙
  • Other simple contradictions such as

    example (x : Nat) (h : x x) : p := p:Sort ?u.5x:Nath:x xp All goals completed! 🐙
🔗tactic
false_or_by_contra

Changes the goal to False, retaining as much information as possible:

  • If the goal is False, do nothing.

  • 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.

  • For a non-propositional goal use False.elim.

14.5.8. 目标管理🔗

🔗tactic
suffices

Given a main goal ctx t, suffices h : t' from e replaces the main goal with ctx t', e must have type t in the context ctx, h : t'.

The variant suffices h : t' by tac is a shorthand for suffices h : t' from by tac. If h : is omitted, the name this is used.

🔗tactic
change
  • change tgt' will change the goal from tgt to tgt', assuming these are definitionally equal.

  • change t' at h will change hypothesis h : t to have type t', assuming assuming t and t' are definitionally equal.

  • change a with b will change occurrences of a to b in the goal, assuming a and b are definitionally equal.

  • change a with b at h similarly changes a to b in the type of hypothesis h.

🔗tactic
generalize
  • 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.

  • generalize e = x at h₁ ... hₙ also generalizes occurrences of e inside h₁, ..., hₙ.

  • generalize e = x at * will generalize occurrences of e everywhere.

🔗tactic
specialize

specialize h a₁ ... aₙ is equivalent to replace h := h a₁ ... 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), p n q n and h' : p 2, then specialize h 2 h' replaces h with h : q 2.

The tactic also supports instantiating particular universal quantifiers using named argument syntax. Example: given h : (m n : Nat), p m n, then specialize h (n := 2) replaces h with h : (m : Nat), p m 2.

🔗tactic
obtain

The obtain tactic is a combination of have and rcases. See rcases for a description of supported patterns.

obtain ⟨patt⟩ : type := proof

is equivalent to

have h : type := proof
rcases h with ⟨patt⟩

If patt is omitted, rcases will try to infer the pattern.

If type is omitted, := proof is required.

🔗tactic
show

show t 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.

🔗tactic
show_term

show_term tac 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.)

14.5.9. 演员管理🔗

本节中的策略可以更轻松地避免卡在 casts 上,这些函数将数据从一种类型强制转换为另一种类型,例如将自然数转换为相应的整数。 Lewis and Madelaine (2020)Robert Y. Lewis and Paul-Nicolas Madelaine, 2020. “Simplifying Casts and Coercions”. arXiv:2001.10594 对它们进行了更详细的描述。

🔗tactic
norm_cast

The norm_cast family of tactics is used to normalize certain coercions (casts) in expressions.

  • norm_cast normalizes casts in the target.

  • norm_cast at h normalizes casts in hypothesis h.

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.

For instance, given an assumption

a b : ℤ
h : ↑a + ↑b < (10 : ℚ)

writing norm_cast at h will turn h into

h : a + b < 10

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):

See also push_cast, which moves casts inwards rather than lifting them outwards.

🔗tactic
push_cast

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.

  • push_cast moves casts inward in the goal.

  • push_cast at h moves casts inward in the hypothesis h. It can be used with extra simp lemmas with, for example, push_cast [Int.add_zero].

Example:

example (a b : Nat) (h1 : ((a + b : Nat) : Int) = 10) (h2 : ((a + b + 0 : Nat) : Int) = 10) : ((a + b : Nat) : Int) = 10 := a:Natb:Nath1:(a + b) = 10h2:(a + b + 0) = 10(a + b) = 10 /- h1 : ↑(a + b) = 10 h2 : ↑(a + b + 0) = 10 ⊢ ↑(a + b) = 10 -/ a:Natb:Nath1:(a + b) = 10h2:(a + b + 0) = 10a + b = 10 /- Now ⊢ ↑a + ↑b = 10 -/ a:Natb:Nath1:a + b = 10h2:(a + b + 0) = 10a + b = 10 a:Natb:Nath1:a + b = 10h2:a + b = 10a + b = 10 /- Now h1 h2 : ↑a + ↑b = 10 -/ All goals completed! 🐙

See also norm_cast.

🔗tactic
exact_mod_cast

Normalize casts in the goal and the given expression, then close the goal with exact.

🔗tactic
apply_mod_cast

Normalize casts in the goal and the given expression, then apply the expression to the goal.

🔗tactic
rw_mod_cast

Rewrites with the given rules, normalizing casts prior to each step.

🔗tactic
assumption_mod_cast

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.

14.5.10. 管理 let 表达式🔗

🔗tactic
extract_lets

Extracts let and have expressions from within the target or a local hypothesis, introducing new local definitions.

  • extract_lets extracts all the lets from the target.

  • extract_lets x y z extracts all the lets from the target and uses x, y, and z for the first names. Using _ for a name leaves it unnamed.

  • extract_lets x y z at h operates on the local hypothesis h instead of the target.

For example, given a local hypotheses if the form h : let x := v; b x, then extract_lets z at h introduces a new local definition z := v and changes h to be h : b z.

🔗tactic
lift_lets

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.

  • lift_lets lifts let expressions in the target.

  • lift_lets at h lifts let expressions at the given local hypothesis.

For example,

example : (let x := 1; x) = 1 := by
  lift_lets
  -- ⊢ let x := 1; x = 1
  ...
🔗tactic
let_to_have

Transforms let expressions into have expressions when possible.

🔗tactic
clear_value
  • clear_value x... 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 have h : x = v := rfl; clear_value x. 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_value x y * ensures that x and y are cleared while trying to clear all other local definitions, and clear_value (hx : x = _) y * with hx does the same while first adding the hx : x = v hypothesis.

14.5.11. 外延性🔗

🔗tactic
ext

Applies extensionality lemmas that are registered with the @[ext] attribute.

  • ext pat* 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.

  • ext pat* : n applies ext theorems only up to depth n.

The ext1 pat* tactic is like ext pat* 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.

🔗tactic
ext1

ext1 pat* is like ext pat* 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.

🔗tactic
apply_ext_theorem

Apply a single extensionality theorem to the current goal.

🔗tactic
funext

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 funext h₁ ... hₙ applies funext n 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.

14.5.12. SMT 启发的自动化🔗

🔗tactic
grind

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.

See the reference manual's chapter on grind for more information.

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 f a = f b for any function f. This forms the foundation for efficient equality reasoning in grind. Here is an example:

example (f : Nat Nat) (h : a = b) : f (f b) = f (f a) := a:Natb:Natf:Nat Nath:a = bf (f b) = f (f a) All goals completed! 🐙

Applying theorems using E-matching

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.

Consider the following functions and theorems:

def f (a : Nat) : Nat := a + 1 def g (a : Nat) : Nat := a - 1 @[grind =] theorem gf (x : Nat) : g (f x) = x := x:Natg (f x) = x All goals completed! 🐙

The theorem gf asserts that g (f x) = x for all natural numbers x. The attribute [grind =] instructs grind to use the left-hand side of the equation, g (f x), 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 g a is not an instance of the pattern g (f x), it becomes one modulo the equation f b = a. By substituting a with f b in g a, we obtain the term g (f b), which matches the pattern g (f x) with the assignment x := b. Thus, the theorem gf is instantiated with x := b, and the new equality g (f b) = b is asserted. grind then uses congruence closure to derive the implied equality g a = g (f b) and completes the proof.

The pattern used to instantiate theorems affects the effectiveness of grind. For example, the pattern g (f x) 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 f x 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:

opaque R : Int Int Prop axiom Rtrans {x y z : Int} : R x y R y z R x z grind_pattern Rtrans => R x y, R y z example {a b c d} : R a b R b c R c d R a d := a:Intb:Intc:Intd:IntR a b R b c R c d R a d All goals completed! 🐙

By specifying the multi-pattern R x y, R y z, we instruct grind to instantiate Rtrans only when both R x y and R y z are available in the context. In the example, grind applies Rtrans to derive R a c from R a b and R b c, and can then repeat the same reasoning to deduce R a d from R a c and R c d.

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 ]

opaque R : Int Int Prop @[grind ] axiom Rtrans {x y z : Int} : R x y R y z R x z example {a b c d} : R a b R b c R c d R a d := a:Intb:Intc:Intd:IntR a b R b c R c d R a d All goals completed! 🐙

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.

  • grind only [<name>, ...] is like grind [<name>, ...] but does not use theorems tagged with @[grind].

  • grind (gen := <num>) sets the maximum generation.

Linear integer arithmetic (lia)

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 {x y : Int} : 2 * x + 4 * y 5 := x:Inty:Int2 * x + 4 * y 5 All goals completed! 🐙 -- Mixing equalities and inequalities. example {x y : Int} : 2 * x + 3 * y = 0 1 x y < 1 := x:Inty:Int2 * x + 3 * y = 0 1 x y < 1 All goals completed! 🐙 -- Reasoning with divisibility. example (a b : Int) : 2 a + 1 2 b + a ¬ 2 b + 2 * a := a:Intb:Int2 a + 1 2 b + a ¬2 b + 2 * a All goals completed! 🐙 example (x y : Int) : 27 11*x + 13*y 11*x + 13*y 45 -10 7*x - 9*y 7*x - 9*y 4 False := x:Inty:Int27 11 * x + 13 * y 11 * x + 13 * y 45 -10 7 * x - 9 * y 7 * x - 9 * y 4 False All goals completed! 🐙 -- Types that implement the `ToInt` type-class. example (a b c : UInt64) : a 2 b 3 c - a - b = 0 c 5 := a:UInt64b:UInt64c:UInt64a 2 b 3 c - a - b = 0 c 5 All 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.

Examples

open Lean Grind example [CommRing α] (x : α) : (x + 1) * (x - 1) = x^2 - 1 := α:Type u_1inst✝:CommRing αx:α(x + 1) * (x - 1) = x ^ 2 - 1 All goals completed! 🐙 -- Characteristic 256 means 16 * 16 = 0. example [CommRing α] [IsCharP α 256] (x : α) : (x + 16) * (x - 16) = x^2 := α:Type u_1inst✝¹:CommRing αinst✝:IsCharP α 256x:α(x + 16) * (x - 16) = x ^ 2 All goals completed! 🐙 -- Works on built-in rings such as `UInt8`. example (x : UInt8) : (x + 16) * (x - 16) = x^2 := x:UInt8(x + 16) * (x - 16) = x ^ 2 All goals completed! 🐙 example [CommRing α] (a b c : α) : a + b + c = 3 a^2 + b^2 + c^2 = 5 a^3 + b^3 + c^3 = 7 a^4 + b^4 = 9 - c^4 := α:Type u_1inst✝:CommRing αa:αb:αc:αa + b + c = 3 a ^ 2 + b ^ 2 + c ^ 2 = 5 a ^ 3 + b ^ 3 + c ^ 3 = 7 a ^ 4 + b ^ 4 = 9 - c ^ 4 All goals completed! 🐙 example [Field α] [NoNatZeroDivisors α] (a : α) : 1 / a + 1 / (2 * a) = 3 / (2 * a) := α:Type u_1inst✝¹:Field αinst✝:NoNatZeroDivisors αa:α1 / a + 1 / (2 * a) = 3 / (2 * a) All goals completed! 🐙

Other options

  • grind (splits := <num>) caps the depth of the search tree. Once a branch performs num splits grind stops splitting further in that branch.

  • grind -splitIte disables case splitting on if-then-else expressions.

  • grind -splitMatch disables case splitting on match expressions.

  • grind +splitImp instructs grind to split on any hypothesis A B whose antecedent A is propositional.

  • grind -linarith disables the linear arithmetic solver for (ordered) modules and rings.

Additional Examples

example {a b} {as bs : List α} : (as ++ bs ++ [b]).getLastD a = b := α:Type u_1a:αb:αas:List αbs:List α(as ++ bs ++ [b]).getLastD a = b All goals completed! 🐙 example (x : BitVec (w+1)) : (BitVec.cons x.msb (x.setWidth w)) = x := w:Natx:BitVec (w + 1)BitVec.cons x.msb (BitVec.setWidth w x) = x All goals completed! 🐙 example (as : Array α) (lo hi i j : Nat) : lo i i < j j hi j < as.size min lo (as.size - 1) i := α:Type u_1as:Array αlo:Nathi:Nati:Natj:Natlo i i < j j hi j < as.size min lo (as.size - 1) i All goals completed! 🐙
🔗tactic
grind?

grind? takes the same arguments as grind, but reports an equivalent call to grind only that would be sufficient to close the goal. This is useful for reducing the size of the grind theorems in a local invocation.

🔗tactic
lia

lia solves linear integer arithmetic goals.

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.

🔗tactic
grobner

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.

14.5.13. 简化🔗

其专用章节中更详细地描述了该简化器。

🔗tactic
simp

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.

  • simp only [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ᵢ.

  • simp at h₁ 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.

  • simp at * simplifies all the hypotheses and the target.

  • simp [*] at * simplifies target and all (propositional) hypotheses using the other hypotheses.

🔗tactic
simp!

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.

🔗tactic
simp?

simp? takes the same arguments as simp, but reports an equivalent call to simp only 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) : (if True then x + 2 else 3) = x + 2 := x:Nat(if True then x + 2 else 3) = x + 2 Try this: [apply] simp only [↓reduceIte, Nat.add_left_cancel_iff]All goals completed! 🐙 -- prints "Try this: simp only [ite_true]"

This command can also be used in simp_all and dsimp.

🔗tactic
simp?!

simp? takes the same arguments as simp, but reports an equivalent call to simp only 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) : (if True then x + 2 else 3) = x + 2 := x:Nat(if True then x + 2 else 3) = x + 2 Try this: [apply] simp only [↓reduceIte, Nat.add_left_cancel_iff]All goals completed! 🐙 -- prints "Try this: simp only [ite_true]"

This command can also be used in simp_all and dsimp.

🔗tactic
simp_arith

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.

🔗tactic
simp_arith!

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.

🔗tactic
dsimp

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.

🔗tactic
dsimp!

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.

🔗tactic
dsimp?

simp? takes the same arguments as simp, but reports an equivalent call to simp only 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) : (if True then x + 2 else 3) = x + 2 := x:Nat(if True then x + 2 else 3) = x + 2 Try this: [apply] simp only [↓reduceIte, Nat.add_left_cancel_iff]All goals completed! 🐙 -- prints "Try this: simp only [ite_true]"

This command can also be used in simp_all and dsimp.

🔗tactic
dsimp?!

simp? takes the same arguments as simp, but reports an equivalent call to simp only 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) : (if True then x + 2 else 3) = x + 2 := x:Nat(if True then x + 2 else 3) = x + 2 Try this: [apply] simp only [↓reduceIte, Nat.add_left_cancel_iff]All goals completed! 🐙 -- prints "Try this: simp only [ite_true]"

This command can also be used in simp_all and dsimp.

🔗tactic
simp_all

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.

🔗tactic
simp_all!

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.

🔗tactic
simp_all?

simp? takes the same arguments as simp, but reports an equivalent call to simp only 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) : (if True then x + 2 else 3) = x + 2 := x:Nat(if True then x + 2 else 3) = x + 2 Try this: [apply] simp only [↓reduceIte, Nat.add_left_cancel_iff]All goals completed! 🐙 -- prints "Try this: simp only [ite_true]"

This command can also be used in simp_all and dsimp.

🔗tactic
simp_all?!

simp? takes the same arguments as simp, but reports an equivalent call to simp only 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) : (if True then x + 2 else 3) = x + 2 := x:Nat(if True then x + 2 else 3) = x + 2 Try this: [apply] simp only [↓reduceIte, Nat.add_left_cancel_iff]All goals completed! 🐙 -- prints "Try this: simp only [ite_true]"

This command can also be used in simp_all and dsimp.

🔗tactic
simp_all_arith

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.

🔗tactic
simp_all_arith!

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.

🔗tactic
simpa

This is a "finishing" tactic modification of simp. It has two forms.

  • simpa [rules, ] using e 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.

🔗tactic
simpa!

This is a "finishing" tactic modification of simp. It has two forms.

  • simpa [rules, ] using e 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.

🔗tactic
simpa?

This is a "finishing" tactic modification of simp. It has two forms.

  • simpa [rules, ] using e 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.

🔗tactic
simpa?!

This is a "finishing" tactic modification of simp. It has two forms.

  • simpa [rules, ] using e 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.

🔗tactic
simp_wf

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.

14.5.14. 重写🔗

🔗tactic
rw

rw is like rewrite, but also tries to close the goal by "cheap" (reducible) rfl afterwards.

🔗tactic
rewrite

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] at l 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 := .pos L) [e], where L : List Nat, 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 := .neg L) allows skipping specified occurrences.

🔗tactic
erw

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).

🔗tactic
rwa

rwa is short-hand for rw; assumption.

🔗structure

Configures the behavior of the rewrite and rw tactics.

Constructor

Lean.Meta.Rewrite.Config.mk

Fields

transparency : Lean.Meta.TransparencyMode

The transparency mode to use for unfolding

offsetCnstrs : Bool

Whether to support offset constraints such as ?x + 1 =?= e

occs : Lean.Meta.Occurrences

Which occurrences to rewrite

newGoals : Lean.Meta.Rewrite.NewGoals

How to convert the resulting metavariables into new goals

🔗inductive type

Configuration for which occurrences that match an expression should be rewritten.

Constructors

Lean.Meta.Occurrences.all : Lean.Meta.Occurrences

All occurrences should be rewritten.

Lean.Meta.Occurrences.pos (idxs : List Nat) :
  Lean.Meta.Occurrences

A list of indices for which occurrences should be rewritten.

Lean.Meta.Occurrences.neg (idxs : List Nat) :
  Lean.Meta.Occurrences

A list of indices for which occurrences should not be rewritten.

🔗inductive type

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, Add Nat 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.

Constructors

Lean.Meta.TransparencyMode.all : Lean.Meta.TransparencyMode

Unfolds all constants, even those tagged as @[irreducible].

Lean.Meta.TransparencyMode.default :
  Lean.Meta.TransparencyMode

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.

Lean.Meta.TransparencyMode.reducible :
  Lean.Meta.TransparencyMode

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.

Lean.Meta.TransparencyMode.instances :
  Lean.Meta.TransparencyMode

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., Add Nat 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).

🔗def

Controls which new mvars are turned in to goals by the apply tactic.

  • nonDependentFirst mvars that don't depend on other goals appear first in the goal list.

  • nonDependentOnly only mvars that don't depend on other goals are added to goal list.

  • all all unassigned mvars are added to the goal list.

🔗tactic
unfold
  • unfold id unfolds all occurrences of definition id in the target.

  • unfold id1 id2 ... is equivalent to unfold id1; unfold id2; ....

  • unfold id at h unfolds at the hypothesis h.

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 simp only [id], which unfolds definition id recursively.

Lean.Elab.Tactic.evalUnfold 实现。

🔗tactic
replace

replace h := e is like have h := e, but it removes a previous hypothesis of the same name as this one if possible. For example, if the state is:

f : α → β
h : α
⊢ goal

Then after replace h := f h the state will be:

f : α → β
h : β
⊢ goal

whereas have h := f h would result in:

f : α → β
h† : α
h : β
⊢ goal

The tactic specialize h a₁ ... aₙ is a way to write replace h := h a₁ ... aₙ, automatically inferring which hypothesis should be replaced.

The replace tactic can be used to simulate Rocq's apply at tactic.

🔗tactic
delta

delta id1 id2 ... delta-expands the definitions id1, id2, .... This is a low-level tactic, it will expose how recursive definitions have been compiled by Lean.

14.5.15. 归纳类型🔗

14.5.15.1. 介绍🔗

🔗tactic
constructor

If the main goal's target type is an inductive type, constructor solves it with the first matching constructor, or else fails.

🔗tactic
injection

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 (c t₁) and (c t₂) 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 injection h adds two new hypothesis with types a = c and b = d to the main goal. The tactic injection h with h₁ h₂ uses the names h₁ and h₂ to name the new hypotheses.

🔗tactic
injections

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).

🔗tactic
left

Applies the first constructor when the goal is an inductive type with exactly two constructors, or fails otherwise.

example : True False := True False True All goals completed! 🐙

14.5.15.2. 消除🔗

消除策略使用 recursors 和自动派生的 casesOn helper 来实现归纳和案例分割。 由这些策略产生的 子目标 由消除器的小前提类型确定,并且使用具有 using 选项的不同消除器会产生不同的子目标。

Choosing Eliminators

当尝试证明 (i : Fin (n + 1)), 0 + i = i 时,引入假设后,策略n:Natval✝:NatisLt✝:val✝ < n + 10 + val✝, isLt✝ = val✝, isLt✝ 结果为:

n:Natval✝:NatisLt✝:val✝ < n + 10 + val✝, isLt✝ = val✝, isLt✝

这是因为 Fin 是具有单个非递归构造函数的 结构。 它的递归器对于这个构造函数有一个小前提:

Fin.rec.{u} {n : Nat} {motive : Fin n Sort u} (mk : (val : Nat) (isLt : val < n) motive val, isLt) (t : Fin n) : motive t

使用策略n:Nat0 + 0 = 0n:Nati✝:Fin na✝:0 + i✝.castSucc = i✝.castSucc0 + i✝.succ = i✝.succ 会导致:

n:Nat0 + 0 = 0n:Nati✝:Fin na✝:0 + i✝.castSucc = i✝.castSucc0 + i✝.succ = i✝.succ

Fin.induction 是一种替代消除器,它在底层 Nat 上实现归纳:

Fin.induction.{u} {n : Nat} {motive : Fin (n + 1) Sort u} (zero : motive 0) (succ : (i : Fin n) motive i.castSucc motive i.succ) (i : Fin (n + 1)) : motive i

自定义消除器 可以使用 induction_eliminatorcases_eliminator 属性进行注册。 消除器是为其显式目标(即消除器函数的显式参数而不是隐式参数)注册的,并且当 inductioncases 用于这些类型的目标时将应用消除器。 当存在时,自定义消除器优先于递归器。 将 tactic.customEliminators 设置为 false 将禁用自定义消除器。

attributeCustom Eliminators

induction_eliminator 属性注册一个消除器以供 induction策略使用。

attr ::= ...
    | induction_eliminator

cases_eliminator 属性注册一个消除器以供 cases策略使用。

attr ::= ...
    | cases_eliminator
🔗tactic
cases

Assuming x is a variable in the local context with an inductive type, cases x 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 : P n and target Q n, cases n produces one goal with hypothesis h : P 0 and target Q 0, and one goal with hypothesis h : P (Nat.succ a) and target Q (Nat.succ a). Here the name a is chosen automatically and is not accessible. You can use with to provide the variables names for each constructor.

  • cases e, where e is an expression instead of a variable, generalizes e in the goal, and then cases on the resulting variable.

  • Given as : List α, cases as with | nil => tac₁ | cons a as' => 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.

  • cases h : 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.

🔗tactic
rcases

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 : x y, trans_rel R x y. Usual usage might be rcases h1 with ha, hb, hc | hd or rcases h2 with 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, 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.

🔗tactic
fun_cases

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_cases f

(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_cases f x y with | case1 => tac₁ | case2 x' ih => tac₂ works like with cases.

Under set_option tactic.fun_induction.unfolding true (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_option tactic.fun_induction.unfolding false, it uses f.fun_cases instead.

🔗tactic
induction

Assuming x is a variable in the local context with an inductive type, induction x 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 : P n and target Q n, induction n produces one goal with hypothesis h : P 0 and target Q 0, and one goal with hypotheses h : P (Nat.succ a) and ih₁ : P a Q a and target Q (Nat.succ a). 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.

  • induction e, where e is an expression instead of a variable, generalizes e in the goal, and then performs induction on the resulting variable.

  • induction e using r 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 C t, where C is a bound variable and t is a (possibly empty) sequence of bound variables

  • induction e generalizing z₁ ... 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, induction x with | zero => tac₁ | succ x' ih => tac₂ uses tactic tac₁ for the zero case, and tac₂ for the succ case.

🔗tactic
fun_induction

The fun_induction tactic is a convenience wrapper around the induction tactic to use the functional induction principle.

The tactic invocation

fun_induction f x₁ ... 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_induction f

(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 forms fun_induction f x y generalizing z₁ ... zₙ and fun_induction f x y with | case1 => tac₁ | case2 x' ih => tac₂ work like with induction.

Under set_option tactic.fun_induction.unfolding true (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_option tactic.fun_induction.unfolding false, it uses f.induct instead.

🔗tactic
nofun

The tactic nofun is shorthand for exact nofun: it introduces the assumptions, then performs an empty pattern match, closing the goal if the introduced pattern is impossible.

🔗tactic
nomatch

The tactic nomatch h is shorthand for exact nomatch h.

库搜索策略旨在用于交互式使用。 运行时,它们会在 Lean 库中搜索引理或重写可能适用于当前情况的规则,并建议新的策略。 这些策略不应留在校样中;相反,他们的建议应该被采纳。

🔗tactic
exact?

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.

🔗tactic
apply?

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.

在这个证明状态下:

i:Natj:Natk:Nath1:i < jh2:j < ki < k

调用 Try this: [apply] exact Nat.lt_trans h1 h2All goals completed! 🐙 表明:

Try this:
  [apply] exact Nat.lt_trans h1 h2
🔗tactic
rw?

rw? tries to find a lemma which can rewrite the goal.

rw? should not be left in proofs; it is a search tool, like apply?.

Suggestions are printed as rw [h] or rw [ h].

You can use rw? [-my_lemma, -my_theorem] to prevent rw? using the named lemmas.

14.5.17. 案例分析🔗

🔗tactic
split

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 if n = 0 then Q else R, 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.

  • split will split the goal (target).

  • split at h will split the hypothesis h.

🔗tactic
by_cases

by_cases (h :)? p splits the main goal into two cases, assuming h : p in the first branch, and h : ¬ p in the second branch.

14.5.18. 决策程序🔗

🔗tactic
decide

decide attempts to prove the main goal (with target type p) by synthesizing an instance of Decidable p and then reducing that instance to evaluate the truth value of p. If it reduces to isTrue h, 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.

Examples

Proving inequalities:

example : 2 + 2 5 := 2 + 2 5 All goals completed! 🐙

Trying to prove a false proposition:

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.

example : 1 + 1 = 2 := 1 + 1 = 2 All goals completed! 🐙 example : 1 + 1 = 2 := 1 + 1 = 2 All goals completed! 🐙
🔗tactic
native_decide

native_decide is a synonym for decide +native. It will attempt to prove a goal of type p by synthesizing an instance of Decidable p 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 #print axioms 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.

example : (List.range 1000).length = 1000 := (List.range 1000).length = 1000 All goals completed! 🐙
🔗tactic
omega

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 x y 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.

The options

omega +splitDisjunctions +splitNatSub +splitNatAbs +splitMinMax

can be used to:

  • splitDisjunctions: split any disjunctions found in the context, if the problem is not otherwise solvable.

  • splitNatSub: for each appearance of ((a - b : Nat) : Int), split on a b if necessary.

  • splitNatAbs: for each appearance of Int.natAbs a, split on 0 a if necessary.

  • splitMinMax: for each occurrence of min a b, split on min a b = a min a b = b Currently, all of these are on by default.

🔗tactic
bv_omega

bv_omega is omega with an additional preprocessor that turns statements about BitVec into statements about Nat. Currently the preprocessor is implemented as try simp only [bitvec_to_nat] at *. bitvec_to_nat is a @[simp] attribute that you can (cautiously) add to more theorems.

14.5.18.1. SAT 求解器集成🔗

🔗tactic
bv_decide

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

  • the Lean equivalent of QF_BV

  • automatically splitting up structures that contain information about BitVec or Bool

example : (a b : BitVec 64), (a &&& b) + (a ^^^ b) = a ||| b := (a b : BitVec 64), (a &&& b) + (a ^^^ b) = a ||| b a✝:BitVec 64b✝:BitVec 64(a✝ &&& b✝) + (a✝ ^^^ b✝) = a✝ ||| b✝ All goals completed! 🐙

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.

Note: include import Std.Tactic.BVDecide

🔗tactic
bv_normalize

Run the normalization procedure of bv_decide only. Sometimes this is enough to solve basic BitVec goals already.

Note: include import Std.Tactic.BVDecide

🔗tactic
bv_check

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:

bv_check "proof.lrat"
🔗tactic
bv_decide?

Suggest a proof script for a bv_decide tactic call. Useful for caching LRAT proofs.

Note: include import Std.Tactic.BVDecide

14.5.19. 按价值评估🔗

cbv策略模拟按值调用评估以减少术语。 在 按值调用计算 中,函数的参数在函数调用减少之前会减少为值。 粗略地说,values 要么是函数,要么是构造函数对值的应用;函数体不需要是一个值,函数本身也可以算作一个值。 此评估策略与 Lean 编译器生成的代码的执行顺序相匹配,这使其非常适合为在运行时良好运行而编写的代码。

cbv 使用 方程引理 展开定义,并应用自动证明 匹配函数 的类似定理,在每一步生成 命题等价 证明。 因为展开是命题性的而不是定义性的,所以 cbv 可以减少通过 良基递归部分固定点 定义的函数。 一般来说,这些函数在定义上并不等于它们的展开,因此内核的定义缩减不会减少它们的递归调用。

cbv 生成的证明仅使用三个标准公理(propextQuot.soundClassical.choice)。 特别是,与 native_decide 不同,它们不需要信任代码生成器的正确性。

由于 cbv 通过 congrArgcongrFun 重写子项,因此它无法重写出现在从属位置的子项。 重写从属函数的参数将改变后续参数的类型,并且即使具有异构相等性,也没有适用于任意从属函数的合适的同余引理。

在减少持续应用时,cbv 按顺序尝试以下策略:

  1. 自定义cbv_eval重写规则

  2. 方程引理(例如,foo.eq_1foo.eq_2

  3. 展开方程

  4. 内核匹配器还原

除非提供匹配的 cbv_eval 重写规则,否则永远不会展开标有 cbv_opaque 的声明。

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
at A 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)*)?
🔗tactic
cbv

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.

Reducing Well-Founded Recursive Functions

函数 countdown 是使用良基递归定义的,因此它在定义上不等于其展开。 普通rfl无法关闭球门:

def countdown (n : Nat) : List Nat := match n with | 0 => [0] | n + 1 => (n + 1) :: countdown n termination_by n example : countdown 3 = [3, 2, 1, 0] := countdown 3 = [3, 2, 1, 0] Tactic `rfl` failed: The left-hand side countdown 3 is not definitionally equal to the right-hand side [3, 2, 1, 0] countdown 3 = [3, 2, 1, 0]countdown 3 = [3, 2, 1, 0]
Tactic `rfl` failed: The left-hand side
  countdown 3
is not definitionally equal to the right-hand side
  [3, 2, 1, 0]

countdown 3 = [3, 2, 1, 0]

cbv策略可以通过命题重写来约简 countdown 3,然后通过 rfl 关闭方程目标:

example : countdown 3 = [3, 2, 1, 0] := countdown 3 = [3, 2, 1, 0] All goals completed! 🐙
Reducing Hypotheses

cbv策略支持标准 at 位置语法。 与 at h 一起使用时,它会减少假设 h 的类型。 当与 at * 一起使用时,它减少了所有非依赖命题 假设和目标。

def countdown (n : Nat) : List Nat := match n with | 0 => [0] | n + 1 => (n + 1) :: countdown n termination_by n example (x : List Nat) (h : x = countdown 2) : x = [2, 1, 0] := x:List Nath:x = countdown 2x = [2, 1, 0] x:List Nath:x = [2, 1, 0]x = [2, 1, 0] All goals completed! 🐙
cbv as a Non-Finishing Tactic

decide 不同,cbv 不是终端策略。 它尽可能地简化目标,但可能留下需要进一步推理的目标。 此处,cbv 减少了对 countdown 的调用,但保留了成员资格目标:

def countdown (n : Nat) : List Nat := match n with | 0 => [0] | n + 1 => (n + 1) :: countdown n termination_by n example : 1 countdown 2 := unsolved goals List.Mem 1 [2, 1, 0]1 countdown 2 List.Mem 1 [2, 1, 0]
unsolved goals
List.Mem 1 [2, 1, 0]
Dependent Positions

函数 wfLengthList.length 的一个版本,它是通过 良基递归 而不是 结构递归 定义的。 结果是 不可约

def wfLength : List Nat Nat | [] => 0 | _ :: xs => wfLength xs + 1 termination_by xs => xs

在非依赖 Std.TreeMap 中,cbv 可以减少计算密钥 wfLength [1, 2]

def myTreeMap : Std.TreeMap Nat Nat := .empty |>.insert (wfLength [1, 2]) 42 example : myTreeMap.toList = [2, 42] := myTreeMap.toList = [(2, 42)] All goals completed! 🐙

但是,考虑一个依赖树映射 FinMap,它将每个键 n 映射到 Fin (n + 1) 类型的值:

abbrev FinMap := Std.DTreeMap Nat (fun n => Fin (n + 1))

这里 cbv 陷入困境,因为值类型 Fin (n + 1) 取决于键:

example : let m : FinMap := .empty |>.insert (wfLength [1, 2]) 0, 0 < wfLength [1, 2] + 1 All goals completed! 🐙 m.toList = [2, 0, m:FinMap := Std.DTreeMap.empty.insert (wfLength [1, 2]) 0, 0 < 2 + 1 All goals completed! 🐙] := unsolved goals [wfLength [1, 2], 0, ] = [2, 0, ]let m := Std.DTreeMap.empty.insert (wfLength [1, 2]) 0, ; Std.DTreeMap.toList m = [2, 0, ] [wfLength [1, 2], 0, ] = [2, 0, ]
unsolved goals
[wfLength [1, 2], 0, ] = [2, 0, ]

14.5.19.1. decide_cbv🔗

🔗tactic
decide_cbv

decide_cbv is a finishing tactic that closes goals of the form p, where p is a Decidable proposition. It proceeds in two steps:

  1. Apply of_decide_eq_true to transform the goal into decide p = true.

  2. Reduce decide p via call-by-value normalization. If the result is definitionally equal to true, the goal is closed.

decide_cbv fails with an error if decide p 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.

decide_cbv

decide_cbv策略通过 按值调用评估 减少 Decidable 实例来关闭可判定命题的目标:

example : 2 + 3 = 5 10 < 20 := 2 + 3 = 5 10 < 20 All goals completed! 🐙

native_decide 不同,decide_cbv 不需要信任代码生成器。 与使用定义约简的 decide 不同,decide_cbv 可以处理由 良基递归 定义的函数:

def isAllPositive : List Int Bool | [] => true | x :: xs => x > 0 && isAllPositive xs termination_by xs => xs example : isAllPositive [1, 2, 3] = true := isAllPositive [1, 2, 3] = true All goals completed! 🐙
Prime Power Testing with decide_cbv

由于 decide_cbv 使用命题展开,因此它可以评估涉及 有充分依据的递归函数的复杂决策过程。 此处,Nat.minFac 查找数字的最小除数,而助手 minFacAux 搜索最小奇数除数:

def minFacAux (n k : Nat) : Nat := if h : n < k * k then n else if h' : k n then k else have : k n := n:Natk:Nath:¬n < k * kh':¬k nk n n:Natk:Nath:¬n < k * kh':¬k nthis:k k * kk n; All goals completed! 🐙 minFacAux n (k + 2) termination_by n + 2 - k def Nat.minFac (n : Nat) : Nat := if 2 n then 2 else minFacAux n 3

Nat.log b n 通过重复平方计算 n 的底 b 对数的下限:

def Nat.log (b n : Nat) : Nat := if b 1 then 0 else (go b n).2 where go : Nat Nat Nat × Nat | _, 0 => (n, 0) | b, fuel + 1 => if n < b then (n, 0) else let (q, e) := go (b * b) fuel if q < b then (q, 2 * e) else (q / b, 2 * e + 1)

这里,即使存在自由变量 kdecide_cbv 也可以减少决策过程的结果:

example : ¬ k, k Nat.log 2 15151515151515 0 < k 15151515151515 = Nat.minFac 15151515151515 ^ k := ¬ k, k Nat.log 2 15151515151515 0 < k 15151515151515 = Nat.minFac 15151515151515 ^ k All goals completed! 🐙

14.5.19.2. 控制 cbv 行为🔗

attributeCustom cbv Rewrite Rules

cbv_eval 属性将定理注册为 cbv 在尝试 方程引理 之前应用的自定义重写规则。 该定理必须是无条件等式;一侧(通常是左侧)必须是常数的应用。

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 从右到左应用规则:

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

自定义重写规则可用于控制 cbv 如何评估特定函数。 例如,反转的简单定义 slowReverse 由于重复使用 List.append 而具有二次复杂度。 通过 fastReverse 提供尾递归表征,cbv 可以有效评估 slowReverse

def slowReverse : List Nat List Nat | [] => [] | x :: xs => slowReverse xs ++ [x] def fastReverse (xs : List Nat) : List Nat := go [] xs where go (acc : List Nat) : List Nat List Nat | [] => acc | x :: xs => go (x :: acc) xs theorem reverse_spec_aux (xs acc : List Nat) : fastReverse.go acc xs = slowReverse xs ++ acc := xs:List Natacc:List NatfastReverse.go acc xs = slowReverse xs ++ acc acc✝:List Natacc✝ = slowReverse [] ++ acc✝acc✝:List Natx✝:Natxs✝:List Natih1✝:fastReverse.go (x✝ :: acc✝) xs✝ = slowReverse xs✝ ++ x✝ :: acc✝fastReverse.go (x✝ :: acc✝) xs✝ = slowReverse (x✝ :: xs✝) ++ acc✝ acc✝:List Natacc✝ = slowReverse [] ++ acc✝acc✝:List Natx✝:Natxs✝:List Natih1✝:fastReverse.go (x✝ :: acc✝) xs✝ = slowReverse xs✝ ++ x✝ :: acc✝fastReverse.go (x✝ :: acc✝) xs✝ = slowReverse (x✝ :: xs✝) ++ acc✝ All goals completed! 🐙 @[cbv_eval] theorem slowReverse_cbv (xs : List Nat) : slowReverse xs = fastReverse xs := xs:List NatslowReverse xs = fastReverse xs All goals completed! 🐙 example : slowReverse [1, 2, 3, 4, 5] = [5, 4, 3, 2, 1] := slowReverse [1, 2, 3, 4, 5] = [5, 4, 3, 2, 1] All goals completed! 🐙
attributeOpaque Declarations for cbv

cbv_opaque 属性阻止 cbv 使用其 方程引理 展开声明或展开定理。 但是,cbv_eval 重写规则始终优先于 cbv_opaque:如果声明存在匹配的 cbv_eval 规则,则即使声明标记为 cbv_opaque,也会应用该规则。 这允许用一组受控的评估规则替换默认的展开行为。

attr ::= ...
    | cbv_opaque
Opaque Definitions with @[cbv_opaque]

countdown 标记为 cbv_opaque 会阻止 cbv 展开它,因此之前由 cbv 关闭的目标现在仍未解决:

def countdown (n : Nat) : List Nat := match n with | 0 => [0] | n + 1 => (n + 1) :: countdown n termination_by n attribute [cbv_opaque] countdown example : countdown 3 = [3, 2, 1, 0] := unsolved goals countdown 3 = [3, 2, 1, 0]countdown 3 = [3, 2, 1, 0] countdown 3 = [3, 2, 1, 0]
unsolved goals
countdown 3 = [3, 2, 1, 0]

14.5.19.2.1. 定制简化程序🔗

cbv 简化过程 (cbv simproc) 是用户定义的元程序,cbv 在与给定模式匹配的子表达式上调用它。 虽然 cbv_eval 规则仅限于静态相等,但 cbv simprocs 可以执行任意计算来决定如何重写子表达式。 常见用例包括定义用于根据文字值或短路控制流评估函数的过程。

cbv 使用的 simproc 具有 Lean.Meta.Sym.Simp.Simproc 类型,该类型不同于 simp策略使用的 Lean.Meta.Simp.Simproc 类型。 这两个系统是独立的:注册 cbv simproc 对 simp 没有影响,反之亦然。

syntaxCustom cbv Simplification Procedures

主体的类型必须为 Simproc(即 Expr SimpM Result)。 该模式是一个有漏洞的表达式 (_),用于确定哪些子表达式触发该过程。 展开可约简定义并对两侧应用 β-、η- 和 z-约简后,模式在结构上与子表达式进行匹配。 匹配是模 α 等价(绑定变量名称被忽略),并且模式中的证明和实例参数被视为通配符。 可选的阶段说明符控制在规范化过程中何时触发该过程。 当未指定阶段时,默认为 (后)。

(预)

cbv 减少每个子表达式之前触发它。争论仍然没有减少。使用此阶段覆盖 cbv 的默认按值调用评估顺序。典型的用例是延迟计算参数或短路计算(如内置 iteOr 过程所做的那样)。

cbv_eval(评估)

在参数已减少到值之后,但在函数展开之前触发。使用此阶段提供有效的地面评估程序。

(后置,默认)

cbv 尝试标准简化(方程引理、展开、内核匹配)后触发。当应首先尝试标准还原时使用此阶段。

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_simproc 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_simproc Use 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_simproc cbv_eval name (pattern) := body

cbv_simproc_decl 变体声明该过程而不激活它。 稍后可以使用 cbv_simproc 激活它。

command ::= ...
    | A `cbv_simproc` declaration without automatically adding it to the cbv simproc set.
To activate, use `attribute [cbv_simproc]`.
cbv_simproc_decl name (pattern) := body
attributeSimplification Procedure Attribute for cbv

cbv_simproc 属性激活先前声明的简化过程(使用 cbv_simproc_decl 定义)以供 cbv 使用。 可选的阶段说明符控制在规范化过程中何时触发该过程。

attr ::= ...
    | cbv_simproc

阶段说明符控制程序何时触发:

attr ::= ...
    | cbv_simproc Use this rewrite rule before entering the subterms 
attr ::= ...
    | cbv_simproc Use this rewrite rule after entering the subterms 
attr ::= ...
    | cbv_simproc cbv_eval
Declaring a cbv_simproc

通过提供 Lean.Meta.Sym.Simp.Simproc 类型的模式和主体来声明简化过程。 该模式是一个有漏洞的表达式 (_),用于确定哪些子表达式触发该过程。 此处,模式为 (myConst _),它与 myConst 的任何应用程序匹配。 过程 (fun _e => do return .rfl) 忽略表达式,返回指示不执行重写的结果。

opaque myConst : Nat Nat open Lean Meta Sym.Simp in cbv_simproc evalMyConst (myConst _) := fun _e => do -- A real simproc would inspect `e`, compute a result, -- and return `.step result proof`. return .rfl

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 变体声明该过程而不激活它。 cbv_simproc 属性可用于稍后激活它,也可以选择在特定阶段激活它:

open Lean Meta Sym.Simp in cbv_simproc_decl evalMyConst2 (myConst _) := fun _e => return .rfl attribute [cbv_simproc cbv_eval] evalMyConst2
Lazy evaluation of a head of the list

这是预阶段简化过程的一个示例,它打破了传统的按值调用求值顺序以实现惰性。 修饰符确保 evalListHead 在计算 List.head? 的参数之前触发。 它使用 List.head?_consList.head? (a :: as) 重写为 some a,丢弃尾部 as 而不对其进行评估。 仅头部元素 a 随后被 cbv 减少。

cbv_simproc evalListHead (List.head? _) := fun e => do let_expr List.head? α listExpr := e | return .rfl let_expr List.cons _ a as := listExpr | return .rfl let Level.succ u Sym.getLevel α | return .rfl let result Sym.share <| mkApp2 (mkConst ``Option.some [u]) α a let proof := mkApp3 (mkConst ``List.head?_cons [u]) α a as return .step result proof theorem cbv_simproc_test : [5 + 5,6].head? = .some 10 := [5 + 5, 6].head? = some 10 All goals completed! 🐙

检查证明项确认简化过程被触发:List.head?_cons 直接出现在证明中,表明 cbv 使用了 simproc 的重写,而不是通过展开其定义来减少 List.head?

theorem cbv_simproc_test : [5 + 5, 6].head? = some 10 :=
of_eq_true
  (Eq.trans (congrFun' (congrArg Eq (Eq.trans List.head?_cons (congrArg some (Eq.refl 10)))) (some 10))
    (eq_self (some 10)))

Lean 包括 cbv 的许多内置简化程序。 这些处理控制流(iteditecondDecidable.decideDecidable.rec)、逻辑连接词(OrAnd)和数据结构操作(数组索引、字符串操作)。 控制流程序使用 (预)阶段来启用短路评估,而阵列和串程序使用 cbv_eval 阶段来直接减少接地应用。

14.5.19.3. 选项🔗

🔗option
cbv.maxSteps

Default value: 100000

Controls the maximum number of steps for the cbv tactic.

🔗option
cbv.warning

Default value: false

When enabled, displays a warning that the cbv tactic is being used.

14.5.20. 控制减少🔗

🔗tactic
with_reducible

with_reducible tacs executes tacs using the reducible transparency setting. In this setting only definitions tagged as [reducible] are unfolded.

🔗tactic
with_reducible_and_instances

with_reducible_and_instances tacs executes tacs using the .instances transparency setting. In this setting only definitions tagged as [reducible] or type class instances are unfolded.

🔗tactic
with_unfolding_all

with_unfolding_all tacs executes tacs using the .all transparency setting. In this setting all definitions that are not opaque are unfolded.

🔗tactic
with_unfolding_none

with_unfolding_none tacs executes tacs using the .none transparency setting. In this setting no definitions are unfolded.

14.5.21. 控制流程🔗

🔗tactic
guard_hyp

Tactic to check that a named hypothesis has a given type and/or value.

  • guard_hyp h : t checks the type up to reducible defeq,

  • guard_hyp h :~ t checks the type up to default defeq,

  • guard_hyp h :ₛ t checks the type up to syntactic equality,

  • guard_hyp h :ₐ t checks the type up to alpha equality.

  • guard_hyp h := v checks value up to reducible defeq,

  • guard_hyp h :=~ v checks value up to default defeq,

  • guard_hyp h :=ₛ v checks value up to syntactic equality,

  • guard_hyp h :=ₐ v checks the value up to alpha equality.

The value v is elaborated using the type of h as the expected type.

🔗tactic
guard_target

Tactic to check that the target agrees with a given expression.

  • guard_target = e checks that the target is defeq at reducible transparency to e.

  • guard_target =~ e checks that the target is defeq at default transparency to e.

  • guard_target =ₛ e checks that the target is syntactically equal to e.

  • guard_target =ₐ e checks that the target is alpha-equivalent to e.

The term e is elaborated with the type of the goal as the expected type, which is mostly useful within conv mode.

🔗tactic
guard_expr

Tactic to check equality of two expressions.

  • guard_expr e = e' checks that e and e' are defeq at reducible transparency.

  • guard_expr e =~ e' checks that e and e' are defeq at default transparency.

  • guard_expr e =ₛ e' checks that e and e' are syntactically equal.

  • guard_expr e =ₐ 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.

🔗tactic
done

done succeeds iff there are no remaining goals.

🔗tactic
sleep

The tactic sleep ms sleeps for ms milliseconds and does nothing. It is used for debugging purposes only.

🔗tactic
stop

stop is a helper tactic for "discarding" the rest of a proof: it is defined as repeat sorry. It is useful when working on the middle of a complex proofs, and less messy than commenting the remainder of the proof.

14.5.22. 术语精化后端🔗

这些策略在精化条款期间使用,以满足产生的义务。

🔗tactic
decreasing_with

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.

🔗tactic
get_elem_tactic

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.

🔗tactic
get_elem_tactic_trivial

get_elem_tactic_trivial has been deprecated in favour of get_elem_tactic_extensible.

14.5.23. 调试实用程序🔗

🔗tactic
sorry

The sorry tactic is a temporary placeholder for an incomplete tactic proof, closing the main goal using exact sorry.

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 #print axioms my_thm command, the axiom used by the implementation of sorry.

🔗tactic
admit

admit is a synonym for sorry.

🔗tactic
dbg_trace

dbg_trace "foo" prints foo when elaborated. Useful for debugging tactic control flow:

example : False True := False True first | False; False; dbg_trace "left" | True; All goals completed! 🐙; dbg_trace "right"
🔗tactic
trace_state

trace_state displays the current state in the info view.

🔗tactic
trace

trace msg displays msg in the info view.

14.5.24. 建议🔗

🔗tactic

(typed as \qed) is a macro that expands to try? in tactic mode.

🔗tactic
suggestions

#suggestions will suggest relevant theorems from the library for the current goal, using the currently registered library suggestion engine.

The suggestions are printed in the order of their confidence, from highest to lowest.

14.5.25. 其他🔗

🔗tactic
trivial

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:

macro_rules | `(tactic| trivial) => `(tactic| simp)
🔗tactic
solve

Similar to first, but succeeds only if one the given tactics solves the current goal.

🔗tactic
and_intros

and_intros applies And.intro until it does not make progress.

🔗tactic
infer_instance

infer_instance is an abbreviation for exact inferInstance. It synthesizes a value of any target type by typeclass inference.

🔗tactic
expose_names

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_option tactic.hygienic false. 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.

🔗tactic
unhygienic

unhygienic tacs 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 := (x : Nat), x = x unhygienic x:Natx = x -- x would normally be intro'd as inaccessible All goals completed! 🐙 -- refer to x
🔗tactic
run_tac

The run_tac doSeq tactic executes code in TacticM Unit.

14.5.26. 验证条件生成🔗

🔗tactic
mvcgen

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 = f a b c 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 def bar_def ... := ... in prog,

  • unfold any method of the instBEqFloat : BEq Float 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 = some 42, 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:

mvcgen [...] invariants
| inv1 _ acc _ => I1 acc
| _ => I2
with grind

This is more convenient than the equivalent · by rename_i _ acc _; exact I1 acc.

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. 策略用于 Std.Do.SPred 中的状态目标🔗

14.5.26.1.1. 启动和停止校样模式🔗

🔗tactic
mstart

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.

🔗tactic
mstop

Stops the stateful proof mode of Std.Do.SPred. This will simply forget all the names given to stateful hypotheses and pretty-print a bit differently.

🔗tactic
mleave

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.

14.5.26.1.2. 证明国家目标🔗

🔗tactic
mspec

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', mspec foo_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 mspec Specs.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 fun s => _ 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), fun s => 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_spec blah ?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.

🔗tactic
mintro

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, mintro h transforms into (hᵢ : Hᵢ)*, (h : P) ⊢ₛ T.

Furthermore, mintro s is like intro s, 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 (mintro pat = (mintro h; mcases h with pat), and can perform multiple introductions in sequence.

🔗tactic
mexact

mexact is like exact, but operating on a stateful Std.Do.SPred goal.

example (Q : SPred σs) : Q ⊢ₛ Q := by
  mstart
  mintro HQ
  mexact HQ
🔗tactic
massumption

massumption is like assumption, but operating on a stateful Std.Do.SPred goal.

example (P Q : SPred σs) : Q ⊢ₛ P → Q := by
  mintro _ _
  massumption
🔗tactic
mrefine

Like refine, but operating on stateful Std.Do.SPred goals.

example (P Q R : SPred σs) : (P ∧ Q ∧ R) ⊢ₛ P ∧ R := by
  mintro ⟨HP, HQ, HR⟩
  mrefine ⟨HP, HR⟩

example (ψ : Nat → SPred σs) : ψ 42 ⊢ₛ ∃ x, ψ x := by
  mintro H
  mrefine ⟨⌜42⌝, H⟩
🔗tactic
mconstructor

mconstructor is like constructor, but operating on a stateful Std.Do.SPred goal.

example (Q : SPred σs) : Q ⊢ₛ Q ∧ Q := by
  mintro HQ
  mconstructor <;> mexact HQ
🔗tactic
mleft

mleft is like left, but operating on a stateful Std.Do.SPred goal.

example (P Q : SPred σs) : P ⊢ₛ P ∨ Q := by
  mintro HP
  mleft
  mexact HP
🔗tactic
mright

mright is like right, but operating on a stateful Std.Do.SPred goal.

example (P Q : SPred σs) : P ⊢ₛ Q ∨ P := by
  mintro HP
  mright
  mexact HP
🔗tactic
mexists

mexists is like exists, but operating on a stateful Std.Do.SPred goal.

example (ψ : Nat → SPred σs) : ψ 42 ⊢ₛ ∃ x, ψ x := by
  mintro H
  mexists 42
🔗tactic
mpure_intro

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 φ.

theorem simple : ⊢ₛ (⌜True⌝ : SPred σs) := by
  mpure_intro
  exact True.intro
🔗tactic
mexfalso

mexfalso is like exfalso, but operating on a stateful Std.Do.SPred goal.

example (P : SPred σs) : ⌜False⌝ ⊢ₛ P := by
  mintro HP
  mexfalso
  mexact HP

14.5.26.1.3. 操纵状态假设🔗

🔗tactic
mclear

mclear is like clear, but operating on a stateful Std.Do.SPred goal.

example (P Q : SPred σs) : P ⊢ₛ Q → Q := by
  mintro HP
  mintro HQ
  mclear HP
  mexact HQ
🔗tactic
mdup

Duplicate a stateful Std.Do.SPred hypothesis.

🔗tactic
mhave

mhave is like have, but operating on a stateful Std.Do.SPred goal.

example (P Q : SPred σs) : P ⊢ₛ (P → Q) → Q := by
  mintro HP HPQ
  mhave HQ : Q := by mspecialize HPQ HP; mexact HPQ
  mexact HQ
🔗tactic
mreplace

mreplace is like replace, but operating on a stateful Std.Do.SPred goal.

example (P Q : SPred σs) : P ⊢ₛ (P → Q) → Q := by
  mintro HP HPQ
  mreplace HPQ : Q := by mspecialize HPQ HP; mexact HPQ
  mexact HPQ
🔗tactic
mspecialize

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Ψ
🔗tactic
mspecialize_pure

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.

example (y : Nat) (P Q : SPred σs) (Ψ : Nat → SPred σs) (hP : ⊢ₛ P) (hΨ : ∀ x, ⊢ₛ P → Q → Ψ x) : ⊢ₛ Q → Ψ (y + 1) := by
  mintro HQ
  mspecialize_pure (hΨ (y + 1)) hP HQ => HΨ
  mexact HΨ
🔗tactic
mcases

Like rcases, but operating on stateful Std.Do.SPred goals. Example: Given a goal h : (P ∧ (Q ∨ R) ∧ (Q → R)) ⊢ₛ R, mcases h with -, hq | hr, hqr will yield two goals: (hq : Q, hqr : Q → R) ⊢ₛ R and (hr : R) ⊢ₛ R.

That is, mcases h with pat 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₂.

🔗tactic
mrename_i

mrename_i is like rename_i, but names inaccessible stateful hypotheses in a Std.Do.SPred goal.

🔗tactic
mpure

mpure moves a pure hypothesis from the stateful context into the pure context.

example (Q : SPred σs) (ψ : φ → ⊢ₛ Q): ⌜φ⌝ ⊢ₛ Q := by
  mintro Hφ
  mpure Hφ
  mexact (ψ Hφ)
🔗tactic
mframe

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