# 5. Tactics¶

In this chapter, we describe an alternative approach to constructing proofs, using *tactics*. A proof term is a representation of a mathematical proof; tactics are commands, or instructions, that describe how to build such a proof. Informally, we might begin a mathematical proof by saying “to prove the forward direction, unfold the definition, apply the previous lemma, and simplify.” Just as these are instructions that tell the reader how to find the relevant proof, tactics are instructions that tell Lean how to construct a proof term. They naturally support an incremental style of writing proofs, in which users decompose a proof and work on goals one step at a time.

We will describe proofs that consist of sequences of tactics as “tactic-style” proofs, to contrast with the ways of writing proof terms we have seen so far, which we will call “term-style” proofs. Each style has its own advantages and disadvantages. For example, tactic-style proofs can be harder to read, because they require the reader to predict or guess the results of each instruction. But they can also be shorter and easier to write. Moreover, tactics offer a gateway to using Lean’s automation, since automated procedures are themselves tactics.

## 5.1. Entering Tactic Mode¶

Conceptually, stating a theorem or introducing a `have`

statement creates a goal, namely, the goal of constructing a term with the expected type. For example, the following creates the goal of constructing a term of type `p ∧ q ∧ p`

, in a context with constants `p q : Prop`

, `hp : p`

and `hq : q`

:

```
theorem test (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p :=
sorry
```

We can write this goal as follows:

```
p : Prop, q : Prop, hp : p, hq : q ⊢ p ∧ q ∧ p
```

Indeed, if you replace the “sorry” by an underscore in the example above, Lean will report that it is exactly this goal that has been left unsolved.

Ordinarily, we meet such a goal by writing an explicit term. But wherever a term is expected, Lean allows us to insert instead a `begin ... end`

block, followed by a sequence of commands, separated by commas. We can prove the theorem above in that way:

```
theorem test (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p :=
begin
apply and.intro,
exact hp,
apply and.intro,
exact hq,
exact hp
end
```

The `apply`

tactic applies an expression, viewed as denoting a function with zero or more arguments. It unifies the conclusion with the expression in the current goal, and creates new goals for the remaining arguments, provided that no later arguments depend on them. In the example above, the command `apply and.intro`

yields two subgoals:

```
p : Prop,
q : Prop,
hp : p,
hq : q
⊢ p
⊢ q ∧ p
```

For brevity, Lean only displays the context for the first goal, which is the one addressed by the next tactic command. The first goal is met with the command `exact hp`

. The `exact`

command is just a variant of `apply`

which signals that the expression given should fill the goal exactly. It is good form to use it in a tactic proof, since its failure signals that something has gone wrong. It is also more robust than `apply`

, since the elaborator takes the expected type, given by the target of the goal, into account when processing the expression that is being applied. In this case, however, `apply`

would work just as well.

You can see the resulting proof term with the `#print`

command:

```
#print test
```

You can write a tactic script incrementally. In VS Code, you can open a window to display messages by pressing `Ctrl-Shift-Enter`

, and that window will then show you the current goal whenever the cursor is in a tactic block. In Emacs, you can see the goal at the end of any line by pressing `C-c C-g`

, or see the remaining goal in an incomplete proof by putting the cursor on the `end`

symbol.

Tactic commands can take compound expressions, not just single identifiers. The following is a shorter version of the preceding proof:

```
theorem test (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p :=
begin
apply and.intro hp,
exact and.intro hq hp
end
```

Unsurprisingly, it produces exactly the same proof term.

```
#print test
```

Tactic applications can also be concatenated with a semicolon. Formally speaking, there is only one (compound) step in the following proof:

```
theorem test (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p :=
begin
apply and.intro hp; exact and.intro hq hp
end
```

See Section 5.5 for a more precise description of the semantics of the semicolon. When a single tactic step can be used to dispell a goal, you can use the `by`

keyword instead of using a `begin...end`

block.

```
theorem test (p q : Prop) (hp : p) (hq : q) : p ∧ q ∧ p :=
by exact and.intro hp (and.intro hq hp)
```

In VS Code, the tactic state will appear in the messages window whenever the cursor is within the contexts of the `by`

. In the Lean Emacs mode, if you put your cursor on the “b” in `by`

and press `C-c C-g`

, Lean shows you the goal that the tactic is supposed to meet.

We will see below that hypotheses can be introduced, reverted, modified, and renamed over the course of a tactic block. As a result, it is impossible for the Lean parser to detect when an identifier that occurs in a tactic block refers to a section variable that should therefore be added to the context. As a result, you need to explicitly tell Lean to include the relevant entities:

```
variables {p q : Prop} (hp : p) (hq : q)
include hp hq
example : p ∧ q ∧ p :=
begin
apply and.intro hp,
exact and.intro hq hp
end
```

The `include`

command tells Lean to include the indicated variables (as well as any variables they depend on) from that point on, until the end of the section or file. To limit the effect of an `include`

, you can use the `omit`

command afterwards:

```
include hp hq
example : p ∧ q ∧ p :=
begin
apply and.intro hp,
exact and.intro hq hp
end
omit hp hq
```

Thereafter, `hp`

and `hq`

are no longer included by default. Alternatively, you can use a section to delimit the scope.

```
section
include hp hq
example : p ∧ q ∧ p :=
begin
apply and.intro hp,
exact and.intro hq hp
end
end
```

Once again, thereafter, `hp`

and `hq`

are no longer included by default. Another workaround is to find a way to refer to the variable in question before entering a tactic block:

```
example : p ∧ q ∧ p :=
let hp := hp, hq := hq in
begin
apply and.intro hp,
exact and.intro hq hp
end
```

Any mention of `hp`

or `hq`

at all outside a tactic block will cause them to be added to the hypotheses.

## 5.2. Basic Tactics¶

In addition to `apply`

and `exact`

, another useful tactic is `intro`

, which introduces a hypothesis. What follows is an example of an identity from propositional logic that we proved Section 3.6, now proved using tactics. We adopt the following convention regarding indentation: whenever a tactic introduces one or more additional subgoals, we indent another two spaces, until the additional subgoals are deleted. That rationale behind this convention, and other structuring mechanisms, will be discussed in Section 5.4 below.

```
example (p q r : Prop) : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
begin
apply iff.intro,
intro h,
apply or.elim (and.right h),
intro hq,
apply or.inl,
apply and.intro,
exact and.left h,
exact hq,
intro hr,
apply or.inr,
apply and.intro,
exact and.left h,
exact hr,
intro h,
apply or.elim h,
intro hpq,
apply and.intro,
exact and.left hpq,
apply or.inl,
exact and.right hpq,
intro hpr,
apply and.intro,
exact and.left hpr,
apply or.inr,
exact and.right hpr
end
```

The `intro`

command can more generally be used to introduce a variable of any type:

```
example (α : Type*) : α → α :=
begin
intro a,
exact a
end
example (α : Type*) : ∀ x : α, x = x :=
begin
intro x,
exact eq.refl x
end
```

It has a plural form, `intros`

, which takes a list of names.

```
example : ∀ a b c : ℕ, a = b → a = c → c = b :=
begin
intros a b c h₁ h₂,
exact eq.trans (eq.symm h₂) h₁
end
```

The `intros`

command can also be used without any arguments, in which case, it chooses names and introduces as many variables as it can. We will see an example of this in a moment.

The `assumption`

tactic looks through the assumptions in context of the current goal, and if there is one matching the conclusion, it applies it.

```
variables x y z w : ℕ
example (h₁ : x = y) (h₂ : y = z) (h₃ : z = w) : x = w :=
begin
apply eq.trans h₁,
apply eq.trans h₂,
assumption -- applied h₃
end
```

It will unify metavariables in the conclusion if necessary:

```
example (h₁ : x = y) (h₂ : y = z) (h₃ : z = w) : x = w :=
begin
apply eq.trans,
assumption, -- solves x = ?m_1 with h₁
apply eq.trans,
assumption, -- solves y = ?m_1 with h₂
assumption -- solves z = w with h₃
end
```

The following example uses the `intros`

command to introduce the three variables and two hypotheses automatically:

```
example : ∀ a b c : ℕ, a = b → a = c → c = b :=
begin
intros,
apply eq.trans,
apply eq.symm,
assumption,
assumption
end
```

There are tactics `reflexivity`

, `symmetry`

, and `transitivity`

, which apply the corresponding operation. Using reflexivity, for example, is more general than writing `apply eq.refl`

, because it works for any relation that has been tagged with the `refl`

attribute. (Attributes will be discussed in Section 6.4.) The `reflexivity`

tactic can also be abbreviated as `refl`

.

```
example (y : ℕ) : (λ x : ℕ, 0) y = 0 :=
begin
refl
end
example (x : ℕ) : x ≤ x :=
begin
refl
end
```

With these tactics, the transitivity proof above can be written more elegantly as follows:

```
example : ∀ a b c : ℕ, a = b → a = c → c = b :=
begin
intros,
transitivity,
symmetry,
assumption,
assumption
end
```

In each case, the use of transitivity introduces a metavariable for the middle term, which is then determined by the later tactics. Alternatively, we can send this middle term as an optional argument to `transitivity`

:

```
example : ∀ a b c : ℕ, a = b → a = c → c = b :=
begin
intros a b c h₁ h₂,
transitivity a,
symmetry,
assumption,
assumption
end
```

The `repeat`

combinator can be used to simplify the last two lines:

```
example : ∀ a b c : ℕ, a = b → a = c → c = b :=
begin
intros,
apply eq.trans,
apply eq.symm,
repeat { assumption }
end
```

The curly braces introduce a new tactic block; they are equivalent to using a nested `begin ... end`

pair, as discussed in the next section.

If some of the goals that are needed to complete the result of an `apply`

depend on others, the `apply`

tactic places those subgoals last, in the hopes that they will be solved implicitly by the solutions to the previous subgoals. For example, consider the following proof:

```
example : ∃ a : ℕ, 5 = a :=
begin
apply exists.intro,
reflexivity
end
```

The first `apply`

requires us to construct two values, namely, a value of `a`

and a proof that `5 = a`

. But the `apply`

tactic takes the second goal to be the more important one, and places it first. Solving it with reflexivity forces `a`

to be instantiated to `5`

, at which point, the second goal is solved automatically.

Sometimes, however, we want to synthesize the necessary arguments in the order that they appear. For that purpose there is a variant of `apply`

called `fapply`

:

```
example : ∃ a : ℕ, a = a :=
begin
fapply exists.intro,
exact 0,
reflexivity
end
```

Here, the command `fapply exists.intro`

leaves two goals. The first requires us to provide a natural number, `a`

, and the second requires us to prove that `a = a`

. The second goal depends on the first, so solving the first goal instantiates a metavariable in the second goal, which we then prove with `reflexivity`

.

Another tactic that is sometimes useful is the `revert`

tactic, which is, in a sense, an inverse to `intro`

.

```
example (x : ℕ) : x = x :=
begin
revert x,
-- goal is ⊢ ∀ (x : ℕ), x = x
intro y,
-- goal is y : ℕ ⊢ y = y
reflexivity
end
```

Moving a hypothesis into the goal yields an implication:

```
example (x y : ℕ) (h : x = y) : y = x :=
begin
revert h,
-- goal is x y : ℕ ⊢ x = y → y = x
intro h₁,
-- goal is x y : ℕ, h₁ : x = y ⊢ y = x
symmetry,
assumption
end
```

But `revert`

is even more clever, in that it will revert not only an element of the context but also all the subsequent elements of the context that depend on it. For example, reverting `x`

in the example above brings `h`

along with it:

```
example (x y : ℕ) (h : x = y) : y = x :=
begin
revert x,
-- goal is y : ℕ ⊢ ∀ (x : ℕ), x = y → y = x
intros,
symmetry,
assumption
end
```

You can also revert multiple elements of the context at once:

```
example (x y : ℕ) (h : x = y) : y = x :=
begin
revert x y,
-- goal is ⊢ ∀ (x y : ℕ), x = y → y = x
intros,
symmetry,
assumption
end
```

You can only `revert`

an element of the local context, that is, a local variable or hypothesis. But you can replace an arbitrary expression in the goal by a fresh variable using the `generalize`

tactic.

```
example : 3 = 3 :=
begin
generalize : 3 = x,
-- goal is x : ℕ ⊢ x = x,
revert x,
-- goal is ⊢ ∀ (x : ℕ), x = x
intro y, reflexivity
end
```

The mnemonic in the notation above is that you are generalizing the goal by setting `3`

to an arbitrary variable `x`

. Be careful: not every generalization preserves the validity of the goal. Here, `generalize`

replaces a goal that could be proved using `reflexivity`

with one that is not provable:

```
example : 2 + 3 = 5 :=
begin
generalize : 3 = x,
-- goal is x : ℕ ⊢ 2 + x = 5,
sorry
end
```

In this example, the `sorry`

tactic is the analogue of the `sorry`

proof term. It closes the current goal, producing the usual warning that `sorry`

has been used. To preserve the validity of the previous goal, the `generalize`

tactic allows us to record the fact that `3`

has been replaced by `x`

. All we need to do is to provide a label, and `generalize`

uses it to store the assignment in the local context:

```
example : 2 + 3 = 5 :=
begin
generalize h : 3 = x,
-- goal is x : ℕ, h : 3 = x ⊢ 2 + x = 5,
rw ←h
end
```

Here the `rewrite`

tactic, abbreviated `rw`

, uses `h`

to replace `x`

by `3`

again. The `rewrite`

tactic will be discussed below.

## 5.3. More Tactics¶

Some additional tactics are useful for constructing and destructing propositions and data. For example, when applied to a goal of the form `p ∨ q`

, the tactics `left`

and `right`

are equivalent to `apply or.inl`

and `apply or.inr`

, respectively. Conversely, the `cases`

tactic can be used to decompose a disjunction.

```
example (p q : Prop) : p ∨ q → q ∨ p :=
begin
intro h,
cases h with hp hq,
-- case hp : p
right, exact hp,
-- case hq : q
left, exact hq
end
```

After `cases h`

is applied, there are two goals. In the first, the hypothesis `h : p ∨ q`

is replaced by `hp : p`

, and in the second, it is replaced by `hq : q`

. The `cases`

tactic can also be used to decompose a conjunction.

```
example (p q : Prop) : p ∧ q → q ∧ p :=
begin
intro h,
cases h with hp hq,
constructor, exact hq, exact hp
end
```

In this example, there is only one goal after the `cases`

tactic is applied, with `h : p ∧ q`

replaced by a pair of assumptions, `hp : p`

and `hq : q`

. The `constructor`

tactic applies the unique constructor for conjunction, `and.intro`

. With these tactics, an example from the previous section can be rewritten as follows:

```
example (p q r : Prop) : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
begin
apply iff.intro,
intro h,
cases h with hp hqr,
cases hqr with hq hr,
left, constructor, repeat { assumption },
right, constructor, repeat { assumption },
intro h,
cases h with hpq hpr,
cases hpq with hp hq,
constructor, exact hp, left, exact hq,
cases hpr with hp hr,
constructor, exact hp, right, exact hr
end
```

We will see in Chapter 7 that these tactics are quite general. The `cases`

tactic can be used to decompose any element of an inductively defined type; `constructor`

always applies the first constructor of an inductively defined type, and `left`

and `right`

can be used with inductively defined types with exactly `two`

constructors. For example, we can use `cases`

and `constructor`

with an existential quantifier:

```
example (p q : ℕ → Prop) : (∃ x, p x) → ∃ x, p x ∨ q x :=
begin
intro h,
cases h with x px,
constructor, left, exact px
end
```

Here, the `constructor`

tactic leaves the first component of the existential assertion, the value of `x`

, implicit. It is represented by a metavariable, which should be instantiated later on. In the previous example, the proper value of the metavariable is determined by the tactic `exact px`

, since `px`

has type `p x`

. If you want to specify a witness to the existential quantifier explicitly, you can use the `existsi`

tactic instead:

```
example (p q : ℕ → Prop) : (∃ x, p x) → ∃ x, p x ∨ q x :=
begin
intro h,
cases h with x px,
existsi x, left, exact px
end
```

Here is another example:

```
example (p q : ℕ → Prop) :
(∃ x, p x ∧ q x) → ∃ x, q x ∧ p x :=
begin
intro h,
cases h with x hpq,
cases hpq with hp hq,
existsi x,
split; assumption
end
```

Here the semicolon after `split`

tells Lean to apply the `assumption`

tactic to both of the goals that are introduced by splitting the conjunction; see Section 5.5 for more information.

These tactics can be used on data just as well as propositions. In the next two examples, they are used to define functions which swap the components of the product and sum types:

```
universes u v
def swap_pair {α : Type u} {β : Type v} : α × β → β × α :=
begin
intro p,
cases p with ha hb,
constructor, exact hb, exact ha
end
def swap_sum {α : Type u} {β : Type v} : α ⊕ β → β ⊕ α :=
begin
intro p,
cases p with ha hb,
right, exact ha,
left, exact hb
end
```

Note that up to the names we have chosen for the variables, the definitions are identical to the proofs of the analogous propositions for conjunction and disjunction. The `cases`

tactic will also do a case distinction on a natural number:

```
open nat
example (P : ℕ → Prop) (h₀ : P 0) (h₁ : ∀ n, P (succ n)) (m : ℕ) :
P m :=
begin
cases m with m', exact h₀, exact h₁ m'
end
```

The `cases`

tactic, and its companion, the `induction`

tactic, are discussed in greater detail in Section 7.6.

The `contradiction`

tactic searches for a contradiction among the hypotheses of the current goal:

```
example (p q : Prop) : p ∧ ¬ p → q :=
begin
intro h, cases h, contradiction
end
```

## 5.4. Structuring Tactic Proofs¶

Tactics often provide an efficient way of building a proof, but long sequences of instructions can obscure the structure of the argument. In this section, we describe some means that help provide structure to a tactic-style proof, making such proofs more readable and robust.

One thing that is nice about Lean’s proof-writing syntax is that it is possible to mix term-style and tactic-style proofs, and pass between the two freely. For example, the tactics `apply`

and `exact`

expect arbitrary terms, which you can write using `have`

, `show`

, and so on. Conversely, when writing an arbitrary Lean term, you can always invoke the tactic mode by inserting a `begin...end`

block. The following is a somewhat toy example:

```
example (p q r : Prop) : p ∧ (q ∨ r) → (p ∧ q) ∨ (p ∧ r) :=
begin
intro h,
exact
have hp : p, from h.left,
have hqr : q ∨ r, from h.right,
show (p ∧ q) ∨ (p ∧ r),
begin
cases hqr with hq hr,
exact or.inl ⟨hp, hq⟩,
exact or.inr ⟨hp, hr⟩
end
end
```

The following is a more natural example:

```
example (p q r : Prop) : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
begin
apply iff.intro,
intro h,
cases h.right with hq hr,
exact or.inl ⟨h.left, hq⟩,
exact or.inr ⟨h.left, hr⟩,
intro h,
cases h with hpq hpr,
exact ⟨hpq.left, or.inl hpq.right⟩,
exact ⟨hpr.left, or.inr hpr.right⟩
end
```

In fact, there is a `show`

tactic, which is the analog of the `show`

keyword in a proof term. It simply declares the type of the goal that is about to be solved, while remaining in tactic mode. Moreover, in tactic mode, `from`

is an alternative name for `exact`

. With the `show`

and `from`

tactics, the previous proof can be written more perspicuously as follows:

```
example (p q r : Prop) : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
begin
apply iff.intro,
intro h,
cases h.right with hq hr,
show (p ∧ q) ∨ (p ∧ r),
from or.inl ⟨h.left, hq⟩,
show (p ∧ q) ∨ (p ∧ r),
from or.inr ⟨h.left, hr⟩,
intro h,
cases h with hpq hpr,
show p ∧ (q ∨ r),
from ⟨hpq.left, or.inl hpq.right⟩,
show p ∧ (q ∨ r),
from ⟨hpr.left, or.inr hpr.right⟩
end
```

Alternatively, you can leave off the `from`

and remain in tactic mode:

```
example (p q r : Prop) : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
begin
apply iff.intro,
intro h,
cases h.right with hq hr,
show (p ∧ q) ∨ (p ∧ r),
{ left, split, exact h.left, assumption },
show (p ∧ q) ∨ (p ∧ r),
{ right, split, exact h.left, assumption },
intro h,
cases h with hpq hpr,
show p ∧ (q ∨ r),
{ cases hpq, split, assumption, left, assumption },
show p ∧ (q ∨ r),
{ cases hpr, split, assumption, right, assumption }
end
```

The `show`

tactic can actually be used to rewrite a goal to something definitionally equivalent:

```
example (n : ℕ) : n + 1 = nat.succ n :=
begin
show nat.succ n = nat.succ n,
reflexivity
end
```

In fact, `show`

does a little more work. When there are multiple goals, you can use `show`

to select which goal you want to work on. Thus both proofs below work:

```
example (p q : Prop) : p ∧ q → q ∧ p :=
begin
intro h,
cases h with hp hq,
split,
show q, from hq,
show p, from hp
end
example (p q : Prop) : p ∧ q → q ∧ p :=
begin
intro h,
cases h with hp hq,
split,
show p, from hp,
show q, from hq
end
```

There is also a `have`

tactic, which introduces a new subgoal, just as when writing proof terms:

```
example (p q r : Prop) : p ∧ (q ∨ r) → (p ∧ q) ∨ (p ∧ r) :=
begin
intro h,
cases h with hp hqr,
show (p ∧ q) ∨ (p ∧ r),
cases hqr with hq hr,
have hpq : p ∧ q,
from and.intro hp hq,
left, exact hpq,
have hpr : p ∧ r,
from and.intro hp hr,
right, exact hpr
end
```

As with `show`

, you can omit the `from`

and stay in tactic mode:

```
example (p q r : Prop) : p ∧ (q ∨ r) → (p ∧ q) ∨ (p ∧ r) :=
begin
intro h,
cases h with hp hqr,
show (p ∧ q) ∨ (p ∧ r),
cases hqr with hq hr,
have hpq : p ∧ q,
split; assumption,
left, exact hpq,
have hpr : p ∧ r,
split; assumption,
right, exact hpr
end
```

As with proof terms, you can omit the label in the `have`

tactic, in which case, the default label `this`

is used:

```
example (p q r : Prop) : p ∧ (q ∨ r) → (p ∧ q) ∨ (p ∧ r) :=
begin
intro h,
cases h with hp hqr,
show (p ∧ q) ∨ (p ∧ r),
cases hqr with hq hr,
have : p ∧ q,
split; assumption,
left, exact this,
have : p ∧ r,
split; assumption,
right, exact this
end
```

You can also use the `have`

tactic with the `:=`

token, which has the same effect as `from`

:

```
example (p q r : Prop) : p ∧ (q ∨ r) → (p ∧ q) ∨ (p ∧ r) :=
begin
intro h,
have hp : p := h.left,
have hqr : q ∨ r := h.right,
show (p ∧ q) ∨ (p ∧ r),
cases hqr with hq hr,
exact or.inl ⟨hp, hq⟩,
exact or.inr ⟨hp, hr⟩
end
```

In this case, the types can be omitted, so we can write `have hp := h.left`

and `have hqr := h.right`

. In fact, with this notation, you can even omit both the type and the label, in which case the new fact is introduced with the label `this`

.

Lean also has a `let`

tactic, which is similar to the `have`

tactic, but is used to introduce local definitions instead of auxiliary facts. It is the tactic analogue of a `let`

in a proof term.

```
example : ∃ x, x + 2 = 8 :=
begin
let a : ℕ := 3 * 2,
existsi a,
reflexivity
end
```

As with `have`

, you can leave the type implicit by writing `let a := 3 * 2`

. The difference between `let`

and `have`

is that `let`

introduces a local definition in the context, so that the definition of the local constant can be unfolded in the proof.

For even more structured proofs, you can nest `begin...end`

blocks within other `begin...end`

blocks. In a nested block, Lean focuses on the first goal, and generates an error if it has not been fully solved at the end of the block. This can be helpful in indicating the separate proofs of multiple subgoals introduced by a tactic.

```
example (p q r : Prop) : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
begin
apply iff.intro,
begin
intro h,
cases h.right with hq hr,
begin
show (p ∧ q) ∨ (p ∧ r),
exact or.inl ⟨h.left, hq⟩
end,
show (p ∧ q) ∨ (p ∧ r),
exact or.inr ⟨h.left, hr⟩
end,
intro h,
cases h with hpq hpr,
begin
show p ∧ (q ∨ r),
exact ⟨hpq.left, or.inl hpq.right⟩
end,
show p ∧ (q ∨ r),
exact ⟨hpr.left, or.inr hpr.right⟩
end
```

Here, we have introduced a new `begin..end`

block whenever a tactic leaves more than one subgoal. You can check that at every line in this proof, there is only one goal visible. Notice that you still need to use a comma after a `begin...end`

block when there are remaining goals to be discharged.

Within a `begin...end`

block, you can abbreviate nested occurrences of `begin`

and `end`

with curly braces:

```
example (p q r : Prop) : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
begin
apply iff.intro,
{ intro h,
cases h.right with hq hr,
{ show (p ∧ q) ∨ (p ∧ r),
exact or.inl ⟨h.left, hq⟩ },
show (p ∧ q) ∨ (p ∧ r),
exact or.inr ⟨h.left, hr⟩ },
intro h,
cases h with hpq hpr,
{ show p ∧ (q ∨ r),
exact ⟨hpq.left, or.inl hpq.right⟩ },
show p ∧ (q ∨ r),
exact ⟨hpr.left, or.inr hpr.right⟩
end
```

This helps explain the convention on indentation we have adopted here: every time a tactic leaves more than one subgoal, we separate the remaining subgoals by enclosing them in blocks and indenting, until we are back down to one subgoal. Thus if the application of theorem `foo`

to a single goal produces four subgoals, one would expect the proof to look like this:

```
begin
apply foo,
{ ... proof of first goal ... },
{ ... proof of second goal ... },
{ ... proof of third goal ... },
proof of final goal
end
```

Another reasonable convention is to enclose *all* the remaining subgoals in indented blocks, including the last one:

```
example (p q r : Prop) : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
begin
apply iff.intro,
{ intro h,
cases h.right with hq hr,
{ show (p ∧ q) ∨ (p ∧ r),
exact or.inl ⟨h.left, hq⟩ },
{ show (p ∧ q) ∨ (p ∧ r),
exact or.inr ⟨h.left, hr⟩ }},
{ intro h,
cases h with hpq hpr,
{ show p ∧ (q ∨ r),
exact ⟨hpq.left, or.inl hpq.right⟩ },
{ show p ∧ (q ∨ r),
exact ⟨hpr.left, or.inr hpr.right⟩ }}
end
```

With this convention, the proof using `foo`

described above would look like this:

```
begin
apply foo,
{ ... proof of first goal ... },
{ ... proof of second goal ... },
{ ... proof of third goal ... },
{ ... proof of final goal ....}
end
```

Both conventions are reasonable. The second convention has the effect that the text in a long proof gradually creeps to the right. Many theorems in mathematics have side conditions that can be dispelled quickly; using the first convention means that the proofs of these side conditions are indented until we return to the “linear” part of the proof.

Combining these various mechanisms makes for nicely structured tactic proofs:

```
example (p q : Prop) : p ∧ q ↔ q ∧ p :=
begin
apply iff.intro,
{ intro h,
have hp : p := h.left,
have hq : q := h.right,
show q ∧ p,
exact ⟨hq, hp⟩ },
intro h,
have hp : p := h.right,
have hq : q := h.left,
show p ∧ q,
exact ⟨hp, hq⟩
end
```

## 5.5. Tactic Combinators¶

*Tactic combinators* are operations that form new tactics from old ones. A sequencing combinator is already implicit in the commas that appear in a `begin...end`

block:

```
example (p q : Prop) (hp : p) : p ∨ q :=
begin left, assumption end
```

This is essentially equivalent to the following:

```
example (p q : Prop) (hp : p) : p ∨ q :=
by { left, assumption }
```

Here, `{ left, assumption }`

is functionally equivalent to a single tactic which first applies `left`

and then applies `assumption`

.

In an expression `t₁; t₂`

, the semicolon provides a *parallel* version of the sequencing operation: `t₁`

is applied to the current goal, and then `t₂`

is applied to *all* the resulting subgoals:

```
example (p q : Prop) (hp : p) (hq : q) : p ∧ q :=
by split; assumption
```

This is especially useful when the resulting goals can be finished off in a uniform way, or, at least, when it is possible to make progress on all of them uniformly.

The *orelse* combinator, denoted `<|>`

, applies one tactic, and then backtracks and applies another one if the first one fails:

```
example (p q : Prop) (hp : p) : p ∨ q :=
by { left, assumption } <|> { right, assumption}
example (p q : Prop) (hq : q) : p ∨ q :=
by { left, assumption } <|> { right, assumption}
```

In the first example, the left branch succeeds, whereas in the second one, it is the right one that succeeds. In the next three examples, the same compound tactic succeeds in each case.

```
example (p q r : Prop) (hp : p) : p ∨ q ∨ r :=
by repeat { {left, assumption} <|> right <|> assumption }
example (p q r : Prop) (hq : q) : p ∨ q ∨ r :=
by repeat { {left, assumption} <|> right <|> assumption }
example (p q r : Prop) (hr : r) : p ∨ q ∨ r :=
by repeat { {left, assumption} <|> right <|> assumption }
```

The tactic tries to solve the left disjunct immediately by assumption; if that fails, it tries to focus on the right disjunct; and if that doesn’t work, it invokes the assumption tactic.

Incidentally, a tactic expression is really a formal term in Lean, of type `tactic α`

for some `α`

. Tactics can be defined and then applied later on.

```
meta def my_tac : tactic unit :=
`[ repeat { {left, assumption} <|> right <|> assumption } ]
example (p q r : Prop) (hp : p) : p ∨ q ∨ r :=
by my_tac
example (p q r : Prop) (hq : q) : p ∨ q ∨ r :=
by my_tac
example (p q r : Prop) (hr : r) : p ∨ q ∨ r :=
by my_tac
```

With a `begin...end`

block or after a `by`

, Lean’s parser uses special mechanisms to parse these expressions, but they are similar to ordinary expressions in Lean like `x + 2`

and `list α`

. (The annotation `[...]`

in the definition of `my_tac`

above invokes the special parsing mechanism here, too.) The book Programming in Lean provides a fuller introduction to writing tactics and installing them for interactive use. The tactic combinators we are discussing here serve as casual entry points to the tactic programming language.

You will have no doubt noticed by now that tactics can fail. Indeed, it is the “failure” state that causes the *orelse* combinator to backtrack and try the next tactic. The `try`

combinator builds a tactic that always succeeds, though possibly in a trivial way: `try t`

executes `t`

and reports success, even if `t`

fails. It is equivalent to `t <|> skip`

, where `skip`

is a tactic that does nothing (and succeeds in doing so). In the next example, the second `split`

succeeds on the right conjunct `q ∧ r`

(remember that disjunction and conjunction associate to the right) but fails on the first. The `try`

tactic ensures that the sequential composition succeeds.

```
example (p q r : Prop) (hp : p) (hq : q) (hr : r) :
p ∧ q ∧ r :=
by split; try {split}; assumption
```

Be careful: `repeat {try t}`

will loop forever, because the inner tactic never fails.

In a proof, there are often multiple goals outstanding. Parallel sequencing is one way to arrange it so that a single tactic is applied to multiple goals, but there are other ways to do this. For example, `all_goals t`

applies `t`

to all open goals:

```
example (p q r : Prop) (hp : p) (hq : q) (hr : r) :
p ∧ q ∧ r :=
begin
split,
all_goals { try {split} },
all_goals { assumption }
end
```

In this case, the `any_goals`

tactic provides a more robust solution.
It is similar to `all_goals`

, except it fails unless its argument
succeeds on at least one goal.

```
example (p q r : Prop) (hp : p) (hq : q) (hr : r) :
p ∧ q ∧ r :=
begin
split,
any_goals { split },
any_goals { assumption }
end
```

The first tactic in the `begin...end`

block below repeatedly splits
conjunctions:

```
example (p q r : Prop) (hp : p) (hq : q) (hr : r) :
p ∧ ((p ∧ q) ∧ r) ∧ (q ∧ r ∧ p) :=
begin
repeat { any_goals { split }},
all_goals { assumption }
end
```

In fact, we can compress the full tactic down to one line:

```
example (p q r : Prop) (hp : p) (hq : q) (hr : r) :
p ∧ ((p ∧ q) ∧ r) ∧ (q ∧ r ∧ p) :=
by repeat { any_goals { split <|> assumption} }
```

The combinators `focus`

and `solve1`

go in the other direction. Specifically, `focus t`

ensures that `t`

only effects the current goal, temporarily hiding the others from the scope. So, if `t`

ordinarily only effects the current goal, `focus { all_goals {t} }`

has the same effect as `t`

. The tactic `solve1 t`

is similar, except that it fails unless `t`

succeeds in solving the goal entirely. The `done`

tactic is also sometimes useful to direct the flow of control; it succeeds only if there are no goals left to be solved.

## 5.6. Rewriting¶

The `rewrite`

tactic (abbreviated `rw`

) and the `simp`

tactic were introduced briefly in Section 4.3. In this section and the next, we discuss them in greater detail.

The `rewrite`

tactic provides a basic mechanism for applying substitutions to goals and hypotheses, providing a convenient and efficient way of working with equality. The most basic form of the tactic is `rewrite t`

, where `t`

is a term whose type asserts an equality. For example, `t`

can be a hypothesis `h : x = y`

in the context; it can be a general lemma, like `add_comm : ∀ x y, x + y = y + x`

, in which the rewrite tactic tries to find suitable instantiations of `x`

and `y`

; or it can be any compound term asserting a concrete or general equation. In the following example, we use this basic form to rewrite the goal using a hypothesis.

```
variables (f : ℕ → ℕ) (k : ℕ)
example (h₁ : f 0 = 0) (h₂ : k = 0) : f k = 0 :=
begin
rw h₂, -- replace k with 0
rw h₁ -- replace f 0 with 0
end
```

In the example above, the first use of `rw`

replaces `k`

with `0`

in the goal `f k = 0`

. Then, the second one replaces `f 0`

with `0`

. The tactic automatically closes any goal of the form `t = t`

. Here is an example of rewriting using a compound expression:

```
example (x y : ℕ) (p : ℕ → Prop) (q : Prop) (h : q → x = y)
(h' : p y) (hq : q) : p x :=
by { rw (h hq), assumption }
```

Here, `h hq`

establishes the equation `x = y`

. The parentheses around `h hq`

are not necessary, but we have added them for clarity.

Multiple rewrites can be combined using the notation `rw [t_1, ..., t_n]`

, which is just shorthand for `rewrite t_1, ..., rewrite t_n`

. The previous example can be written as follows:

```
variables (f : ℕ → ℕ) (k : ℕ)
example (h₁ : f 0 = 0) (h₂ : k = 0) : f k = 0 :=
by rw [h₂, h₁]
```

By default, `rw`

uses an equation in the forward direction, matching the left-hand side with an expression, and replacing it with the right-hand side. The notation `←t`

can be used to instruct the tactic to use the equality `t`

in the reverse direction.

```
variables (f : ℕ → ℕ) (a b : ℕ)
example (h₁ : a = b) (h₂ : f a = 0) : f b = 0 :=
begin
rw [←h₁, h₂]
end
```

In this example, the term `←h₁`

instructs the rewriter to replace `b`

with `a`

. In the editors, you can type the backwards arrow as `\l`

. You can also use the ascii equivalent, `<-`

.

Sometimes the left-hand side of an identity can match more than one subterm in the pattern, in which case the `rewrite`

tactic chooses the first match it finds when traversing the term. If that is not the one you want, you can use additional arguments to specify the appropriate subterm.

```
import data.nat.basic
example (a b c : ℕ) : a + b + c = a + c + b :=
begin
rw [add_assoc, add_comm b, ←add_assoc]
end
example (a b c : ℕ) : a + b + c = a + c + b :=
begin
rw [add_assoc, add_assoc, add_comm b]
end
example (a b c : ℕ) : a + b + c = a + c + b :=
begin
rw [add_assoc, add_assoc, add_comm _ b]
end
```

In the first example above, the first step rewrites `a + b + c`

to `a + (b + c)`

. Then next applies commutativity to the term `b + c`

; without specifying the argument, the tactic would instead rewrite `a + (b + c)`

to `(b + c) + a`

. Finally, the last step applies associativity in the reverse direction rewriting `a + (c + b)`

to `a + c + b`

. The next two examples instead apply associativity to move the parenthesis to the right on both sides, and then switch `b`

and `c`

. Notice that the last example specifies that the rewrite should take place on the right-hand side by specifying the second argument to `add_comm`

.

By default, the `rewrite`

tactic affects only the goal. The notation `rw t at h`

applies the rewrite `t`

at hypothesis `h`

.

```
variables (f : ℕ → ℕ) (a : ℕ)
example (h : a + 0 = 0) : f a = f 0 :=
by { rw add_zero at h, rw h }
```

The first step, `rw add_zero at h`

, rewrites the hypothesis `a + 0 = 0`

to `a = 0`

. Then the new hypothesis `a = 0`

is used to rewrite the goal to `f 0 = f 0`

.

The `rewrite`

tactic is not restricted to propositions. In the following example, we use `rw h at t`

to rewrite the hypothesis `t : tuple α n`

to `v : tuple α 0`

.

```
def tuple (α : Type*) (n : ℕ) :=
{ l : list α // list.length l = n }
variables {α : Type*} {n : ℕ}
example (h : n = 0) (t : tuple α n) : tuple α 0 :=
begin
rw h at t,
exact t
end
```

Note that the rewrite tactic can carry out generic calculations in any algebraic structure. The following examples involve an arbitrary ring and an arbitrary group, respectively.

```
import algebra.ring
example {α : Type*} [ring α] (a b c : α) :
a * 0 + 0 * b + c * 0 + 0 * a = 0 :=
begin
rw [mul_zero, mul_zero, zero_mul, zero_mul],
repeat { rw add_zero }
end
example {α : Type*} [group α] {a b : α} (h : a * b = 1) :
a⁻¹ = b :=
by rw [←(mul_one a⁻¹), ←h, inv_mul_cancel_left]
```

Using the type class mechanism described in Chapter 10, Lean identifies both abstract and concrete instances of the relevant algebraic structures, and instantiates the relevant facts accordingly.

## 5.7. Using the Simplifier¶

Whereas `rewrite`

is designed as a surgical tool for manipulating a goal, the simplifier offers a more powerful form of automation. A number of identities in Lean’s library have been tagged with the `[simp]`

attribute, and the `simp`

tactic uses them to iteratively rewrite subterms in an expression.

```
import data.nat.basic
variables (x y z : ℕ) (p : ℕ → Prop)
variable (h : p (x * y))
example : (x + 0) * (0 + y * 1 + z * 0) = x * y :=
by simp
include h
example : p ((x + 0) * (0 + y * 1 + z * 0)) :=
by { simp, assumption }
```

In the first example, the left-hand side of the equality in the goal is simplified using the usual identities involving 0 and 1, reducing the goal to `x * y = x * y`

. At that point, `simp`

applies reflexivity to finish it off. In the second example, `simp`

reduces the goal to `p (x * y)`

, at which point the assumption `h`

finishes it off. (Remember that we have to `include h`

explicitly because it is not explicitly mentioned.) Here are some more examples with lists:

```
import data.list.basic
variable {α : Type*}
open list
example (xs : list ℕ) :
reverse (xs ++ [1, 2, 3]) = [3, 2, 1] ++ reverse xs :=
by simp
example (xs ys : list α) :
length (reverse (xs ++ ys)) = length xs + length ys :=
by simp [add_comm]
```

This example uses facts about lists that are found in Lean’s mathematics library, which we need to explicitly import.

As with `rw`

, you can use the keyword `at`

to simplify a hypothesis:

```
variables (x y z : ℕ) (p : ℕ → Prop)
example (h : p ((x + 0) * (0 + y * 1 + z * 0))) :
p (x * y) :=
by { simp at h, assumption }
```

Moreover, you can use a “wildcard” asterisk to simplify all the hypotheses and the goal:

```
variables (w x y z : ℕ) (p : ℕ → Prop)
local attribute [simp] mul_comm mul_assoc mul_left_comm
local attribute [simp] add_assoc add_comm add_left_comm
example (h : p (x * y + z * w * x)) : p (x * w * z + y * x) :=
by { simp at *, assumption }
example (h₁ : p (1 * x + y)) (h₂ : p (x * z * 1)) :
p (y + 0 + x) ∧ p (z * x) :=
by { simp at *, split; assumption }
```

For operations that are commutative and associative, like multiplication on the natural numbers, the simplifier uses these two facts to rewrite an expression, as well as *left commutativity*. In the case of multiplication the latter is expressed as follows: `x * (y * z) = y * (x * z)`

. The `local attribute`

command tells the simplifier to use these rules in the current file (or section or namespace, as the case may be). It may seem that commutativity and left-commutativity are problematic, in that repeated application of either causes looping. But the simplifier detects identities that permute their arguments, and uses a technique known as *ordered rewriting*. This means that the system maintains an internal ordering of terms, and only applies the identity if doing so decreases the order. With the three identities mentioned above, this has the effect that all the parentheses in an expression are associated to the right, and the expressions are ordered in a canonical (though somewhat arbitrary) way. Two expressions that are equivalent up to associativity and commutativity are then rewritten to the same canonical form.

```
example : x * y + z * w * x = x * w * z + y * x :=
by simp
example (h : p (x * y + z * w * x)) : p (x * w * z + y * x) :=
begin simp, simp at h, assumption end
```

As with the rewriter, the simplifier behaves appropriately in algebraic structures:

```
import algebra.ring
variables {α : Type*} [comm_ring α]
local attribute [simp] mul_comm mul_assoc mul_left_comm
local attribute [simp] add_assoc add_comm add_left_comm
example (x y z : α) : (x - x) * y + z = z :=
begin simp end
example (x y z w : α) : x * y + z * w * x = x * w * z + y * x :=
by simp
```

As with `rewrite`

, you can send `simp`

a list of facts to use, including general lemmas, local hypotheses, definitions to unfold, and compound expressions. The `simp`

tactic does not recognize the `←t`

syntax that `rewrite`

does, so to use an identity in the other direction you need to use `eq.symm`

explicitly. In any case, the additional rules are added to the collection of identities that are used to simplify a term.

```
def f (m n : ℕ) : ℕ := m + n + m
example {m n : ℕ} (h : n = 1) (h' : 0 = m) : (f m n) = n :=
by simp [h, h'.symm, f]
```

A common idiom is to simplify a goal using local hypotheses:

```
variables (f : ℕ → ℕ) (k : ℕ)
example (h₁ : f 0 = 0) (h₂ : k = 0) : f k = 0 :=
by simp [h₁, h₂]
```

To use all the hypotheses present in the local context when simplifying, we can use the wildcard symbol, `*`

:

```
example (h₁ : f 0 = 0) (h₂ : k = 0) : f k = 0 :=
by simp *
```

Here is another example:

```
import data.nat.basic
example (u w x y z : ℕ) (h₁ : x = y + z) (h₂ : w = u + x) :
w = z + y + u :=
by simp [*, add_assoc, add_comm, add_left_comm]
```

The simplifier will also do propositional rewriting. For example, using the hypothesis `p`

, it rewrites `p ∧ q`

to `q`

and `p ∨ q`

to `true`

, which it then proves trivially. Iterating such rewrites produces nontrivial propositional reasoning.

```
variables (p q r : Prop)
example (hp : p) : p ∧ q ↔ q :=
by simp *
example (hp : p) : p ∨ q :=
by simp *
example (hp : p) (hq : q) : p ∧ (q ∨ r) :=
by simp *
```

The next example simplifies all the hypotheses, and then uses them to prove the goal.

```
import data.nat.basic
variables (u w x x' y y' z : ℕ) (p : ℕ → Prop)
example (h₁ : x + 0 = x') (h₂ : y + 0 = y') :
x + y + 0 = x' + y' :=
by { simp at *, simp * }
```

One thing that makes the simplifier especially useful is that its capabilities can grow as a library develops. For example, suppose we define a list operation that symmetrizes its input by appending its reversal:

```
import data.list.basic
open list
variables {α : Type*} (x y z : α) (xs ys zs : list α)
def mk_symm (xs : list α) := xs ++ reverse xs
```

Then for any list `xs`

, `reverse (mk_symm xs)`

is equal to `mk_symm xs`

, which can easily be proved by unfolding the definition:

```
theorem reverse_mk_symm (xs : list α) :
reverse (mk_symm xs) = mk_symm xs :=
by { unfold mk_symm, simp }
```

Or even more simply,

```
theorem reverse_mk_symm (xs : list α) :
reverse (mk_symm xs) = mk_symm xs :=
by simp [mk_symm]
```

We can now use this theorem to prove new results:

```
example (xs ys : list ℕ) :
reverse (xs ++ mk_symm ys) = mk_symm ys ++ reverse xs :=
by simp [reverse_mk_symm]
example (xs ys : list ℕ) (p : list ℕ → Prop)
(h : p (reverse (xs ++ (mk_symm ys)))) :
p (mk_symm ys ++ reverse xs) :=
by simp [reverse_mk_symm] at h; assumption
```

But using `reverse_mk_symm`

is generally the right thing to do, and it would be nice if users did not have to invoke it explicitly. We can achieve that by marking it as a simplification rule when the theorem is defined:

```
@[simp] theorem reverse_mk_symm (xs : list α) :
reverse (mk_symm xs) = mk_symm xs :=
by simp [mk_symm]
example (xs ys : list ℕ) :
reverse (xs ++ mk_symm ys) = mk_symm ys ++ reverse xs :=
by simp
example (xs ys : list ℕ) (p : list ℕ → Prop)
(h : p (reverse (xs ++ (mk_symm ys)))) :
p (mk_symm ys ++ reverse xs) :=
by simp at h; assumption
```

The notation `@[simp]`

declares `reverse_mk_symm`

to have the `[simp]`

attribute, and can be spelled out more explicitly:

```
attribute [simp]
theorem reverse_mk_symm (xs : list α) :
reverse (mk_symm xs) = mk_symm xs :=
by simp [mk_symm]
```

The attribute can also be applied any time after the theorem is declared:

```
theorem reverse_mk_symm (xs : list α) :
reverse (mk_symm xs) = mk_symm xs :=
by simp [mk_symm]
attribute [simp] reverse_mk_symm
example (xs ys : list ℕ) :
reverse (xs ++ mk_symm ys) = mk_symm ys ++ reverse xs :=
by simp
example (xs ys : list ℕ) (p : list ℕ → Prop)
(h : p (reverse (xs ++ (mk_symm ys)))) :
p (mk_symm ys ++ reverse xs) :=
by simp at h; assumption
```

Once the attribute is applied, however, there is no way to remove it; it persists in any file that imports the one where the attribute is assigned. As we will discuss further in Section 6.4, one can limit the scope of an attribute to the current file or section using the `local attribute`

command:

```
section
local attribute [simp] reverse_mk_symm
example (xs ys : list ℕ) :
reverse (xs ++ mk_symm ys) = mk_symm ys ++ reverse xs :=
by simp
example (xs ys : list ℕ) (p : list ℕ → Prop)
(h : p (reverse (xs ++ (mk_symm ys)))) :
p (mk_symm ys ++ reverse xs) :=
by simp at h; assumption
end
```

Outside the section, the simplifier will no longer use `reverse_mk_symm`

by default.

You can even create your own sets of simplifier rules, to be applied in special situations.

```
run_cmd mk_simp_attr `my_simps
attribute [my_simps] reverse_mk_symm
example (xs ys : list ℕ) :
reverse (xs ++ mk_symm ys) = mk_symm ys ++ reverse xs :=
by simp with my_simps
example (xs ys : list ℕ) (p : list ℕ → Prop)
(h : p (reverse (xs ++ (mk_symm ys)))) :
p (mk_symm ys ++ reverse xs) :=
by simp with my_simps at h; assumption
```

The command `run_cmd mk_simp_attr `my_simps`

creates a new attribute `[my_simps]`

. (The backtick is used to indicate that `my_simps`

is a new name, something that is explained more fully in Programming in Lean.) The command `simp with my_simps`

then adds all the theorems that have been marked with attribute `[my_simps]`

to the default set of theorems marked with attribute `[simp]`

before applying `[simp]`

, and similarly with `simp with my_simps at h`

.

Note that the various `simp`

options we have discussed — giving an explicit list of rules, using `at`

to specify the location, and using `with`

to add additional simplifier rules — can be combined, but the order they are listed is rigid. You can see the correct order in an editor by placing the cursor on the `simp`

identifier to see the documentation string that is associated with it.

There are two additional modifiers that are useful. By default, `simp`

includes all theorems that have been marked with the attribute `[simp]`

. Writing `simp only`

excludes these defaults, allowing you to use a more explicitly crafted list of rules. Alternatively, writing `simp without t`

filters `t`

and removes it from the set of simplification rules. In the examples below, the minus sign and `only`

are used to block the application of `reverse_mk_symm`

.

```
attribute [simp] reverse_mk_symm
example (xs ys : list ℕ) (p : list ℕ → Prop)
(h : p (reverse (xs ++ (mk_symm ys)))) :
p (mk_symm ys ++ reverse xs) :=
by { simp at h, assumption }
example (xs ys : list ℕ) (p : list ℕ → Prop)
(h : p (reverse (xs ++ (mk_symm ys)))) :
p (reverse (mk_symm ys) ++ reverse xs) :=
by { simp [-reverse_mk_symm] at h, assumption }
example (xs ys : list ℕ) (p : list ℕ → Prop)
(h : p (reverse (xs ++ (mk_symm ys)))) :
p (reverse (mk_symm ys) ++ reverse xs) :=
by { simp only [reverse_append] at h, assumption }
```