Skip to content

feat: add more tactic examples and documentation#843

Open
jcreedcmu wants to merge 17 commits into
leanprover:mainfrom
jcreedcmu:jcreed/tactics
Open

feat: add more tactic examples and documentation#843
jcreedcmu wants to merge 17 commits into
leanprover:mainfrom
jcreedcmu:jcreed/tactics

Conversation

@jcreedcmu

Copy link
Copy Markdown
Contributor

No description provided.

@david-christiansen david-christiansen added the HTML available HTML has been generated for this PR label May 15, 2026
Comment thread Manual/Tactics/Reference.lean Outdated
Comment on lines +80 to +81
The {tactic}`intro` tactic makes progress on goals whose target type is a function type or a universal quantifier.
It introduces the function's parameter into the local context as a new assumption (for propositions) or a new local variable (for data) and changes the goal to the function's body.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to call out implication here?

It seems to me that centering function types is the wrong way to present this, and that it'd be better to lead with universal quantifiers, then implications; we're not really in the business of producing computable functions with tactics, but it's fine to have that as the third alternative.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, agree that centering the propositional angle is better.

Comment thread Manual/Tactics/Reference.lean Outdated
Comment thread Manual/Tactics/Reference.lean Outdated
:::

:::example "Anonymous Introduction"
When called with no arguments, {tactic}`intro` introduces one parameter using the binder's name if available:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should mention hygiene (it's really the binder's name plus macro scopes, right?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't actually have any clear idea how intro interacts with macro scopes. Is there another section of the manual that I should be linking to?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, it does the usual macro introduction thing. If the identifier occurs in the syntax that the user wrote, then it's available for reference in further things the user wrote. If the identifier was produced by metaprogram internals (including "give me a default name from this internal binder name"), then it can't be referenced from things the user wrote. And different metaprograms (and invocations of the same metaprogram) get distinct local namespaces as well, preventing painful capture issues.

Comment thread Manual/Tactics/Reference.lean Outdated
tag := "tactic-ref-relations"
%%%

The {tactic}`rfl` tactic succeeds whenever the two sides of the relation are {tech (key := "definitional equality")}[definitionally equal], even if they are not syntactically identical.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also works for other reflexive relations. Should we center that and have an example?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, there is an example. Mention it here, perhaps?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea!

example (P : Prop) : P ↔ P := by
rfl
```
:::

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about adding another reflexive relation in an example? Like:

def Univ (_ _ : α) := True

@[refl]
theorem Univ.refl (x : α) : Univ x x := True.intro

example : Univ 2 2 := by rfl

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea.

symm
exact h
```
:::

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should also be an example of the symm attribute on a relation here.

Perhaps something like:

def Univ (_ _ : α) := True

@[symm]
theorem Univ.symm (x : α) : Univ x y → Univ y x := by
  intro; exact True.intro

example : Univ 2 3 → Univ 3 2 := by
  intro
  symm
  assumption

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread Manual/Tactics/Reference.lean Outdated
exact h
```
Using uncontrolled {tactic}`congr` would have left us with the goal `a = b`,
which we cannot close, because even though `b * c = a * c`, it might be because `c` is zero.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"close" is not the verb we usually use in this manual. What about "which is unprovable"?

Also, this sentence is long. Can it be two sentences?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread Manual/Tactics/Reference.lean Outdated
tag := "tactic-ref-lemmas"
%%%

The {tactic}`exact` tactic closes the current goal by providing a term whose type matches the goal's target type.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is "closes" the right verb?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going with "prove" here.

Comment thread Manual/Tactics/Reference.lean Outdated
Comment on lines +415 to +416
· exact hab
· exact hbc

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about apply here, for extra oomph?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sgtm.

:::

Note that {tactic}`apply` does not work directly with `↔` (if-and-only-if) hypotheses.
To use a hypothesis `h : P ↔ Q` backwards on the goal, use {tactic}`rw` instead, or extract one direction with `h.mp` or `h.mpr`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could use an example.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

To use a hypothesis `h : P ↔ Q` backwards on the goal, use {tactic}`rw` instead, or extract one direction with `h.mp` or `h.mpr`.

The {tactic}`refine` tactic is like {tactic}`exact`, but allows holes written as `?_` that become new goals.
This is useful when part of a term is known but some arguments still need to be proved.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also nice when the proof being applied isn't just a lemma that mostly matches, but is built up with things like anonymous constructor syntax.

Comment thread Manual/Tactics/Reference.lean Outdated
Comment thread Manual/Tactics/Reference.lean Outdated
Comment thread Manual/Tactics/Reference.lean Outdated
Comment thread Manual/Tactics/Reference.lean Outdated
:::

:::example "Unfolding a Definition"
Because `¬P` is defined as `P → False`, {tactic}`change` can make this explicit:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This strikes me as a strange use of change, because normally I'd use rw [Not] or perhaps unfold [Not] for this kind of thing.

What about instead proving that x + 2 = 1 + x + 1 by first changeing it to x + 1 + 1 and then applying associativity? That seems similarly small, but it matches the pattern of "put it into this other form from the same defeq equivalence class that my solver can deal with"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to keep pushing back if you want to, but I would have found this example helpful when I was learning to know that change can let the user deliberately set the goal to exactly the defeq equivalence class representative they want to, even if there are other ways to use other tactics to get to the same endpoint. But the 2 defeq 1+1 is a fine example as well, and I'd be enthusiastic about including both.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both makes sense. I guess the key is that many defeq rules read like directional reduction steps (beta or delta, for instance), but defeq is an equality and change works "backwards" too.

Comment thread Manual/Tactics/Reference.lean Outdated

The {tactic}`specialize` tactic instantiates a universally quantified or function-typed hypothesis with specific arguments, replacing it in the context with the result.
Because {tactic}`specialize` modifies the hypothesis in place, the original general statement is lost after specialization.
If the original hypothesis is needed again, use {tactic}`have` to create a copy first, for example `have h' := h` before specializing `h`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or use have to create the specialized version!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True! Fixed.

:::

The {tactic}`obtain` tactic is used when a hypothesis or proof term has internal structure that should be broken apart.
Where {tactic}`have` introduces a single new fact into the context, {tactic}`obtain` destructs a term into its pieces using pattern matching. For example, extracting the witness from an existential or the two sides of a conjunction.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also uses the rcases pattern language, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not knowledgeable enough to know what is the best thing to include here to that effect textually.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now, the rcases pattern language is described in the rcases docstring. This is not good - it should be described in a manual section and linked to from docstrings. I just haven't gotten to it yet. It would be really useful to extract that to a section that all these can xref.

Comment thread Manual/Tactics/Reference.lean Outdated
```
:::

The {tactic}`show` tactic selects a goal whose target unifies with the given type and makes it the current goal.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We actually don't get a lot into unification in these docs. It might be nice to unpack this a bit to increase accessibility for those not well-versed in PL implementation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I currently feel like that's another topic out of scope of "improve tactic documentation".

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean "unpack" as in "use a few more words so this can make sense to nonexperts", not "fully define unification". Like "the proposed goal is matched against available goals modulo defeq and assigning of metas/holes", and then include an example that actually demonstrates this.


:::example "Selecting a Goal"
When there are multiple goals, {tactic}`show` brings a specific one to the front.
Here, after {tactic}`constructor` the goals are `⊢ P` then `⊢ Q`, but `show Q` reorders them:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If unification is important here then we should have an example that uses it more obviously.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also feel here this is a thing that would be an improvement in general but I'd prefer to keep it out of the scope of this PR.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this example is sufficient to demonstrate the key principles:

example : 1 ≠ 2 ∧ "two" ≠ "three" := by
  constructor
  show "two" = _ → False

Is that really too much for the PR? It demonstrates that the matching is modulo defeq (because inequality is unfolded to negation, and that to implication) and holes (because _ replaces "three")

Comment thread Manual/Tactics/Reference.lean Outdated
%%%

The {tactic}`ext` tactic applies extensionality lemmas registered with the {attr}`ext` attribute.
The principle of extensionality states that two objects are equal if they are built from the same components. For example, two functions are equal if they return the same value on every input.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's what extensionality means. Rather, it means that two terms are equal if they denote the same underlying mathematical object, right? So eta is a kind of extensional equality, and funext is a more general form, but that's because the mathematical community considers "maps equal elements of the domain to equal elements of the range" to be what it means for two functions to be equal. Similarly, we could create a quotient that collapses Nat into even and odd equivalence classes, and extensionality for that type would be precisely this equivalence class. "Nothing can distinguish them means they're equal" is another way to understand it, which I think makes most sense as an intuition here. That captures funext, but also principles like Array.ext that says that the arrays have the same size and assign equal valid indices to equal values, or structure ext theorems that are propositional versions of structure eta.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logical equivalence is also extensional equality for Prop, as witnessed by propext. That definitely doesn't fit "built from the same components" - (2 = 2) = True is a theorem of Lean, for example.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm here I agree that my wording is insufficent to cover propext, but I do like offering an intuition that works for some cases. Will try to reword...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think appealing to Leibniz equality is too tautologous to really feel to me like it's about extensionality. A beta-reduced term has all the properties of the beta-redex, but the reason they are equal is via beta-reduction, not via eta.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about: "Extensionality properties say that two objects are equal if they are equal under all appropriate observations.
For example, two functions are equal if they return the same value on every input and two pairs are equal if their components are equal."

For me, "appropriate" is doing a lot of work in that sentence, and that's ok :-)
I think this phrasing apples to Array.ext and propext, but I'm also ok with those example being silent.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, they're intensionally equal due to beta, but they can be extensionally equal as well due to something like funext or propext or the eta-rule for unit-like types. We can prove that id () = () by appeal to rfl and thus defeq via delta and beta, but we can also prove it via something like Unit.ext (x : Unit) x = () (which we have definitionally in Lean too, but pretend we don't for a moment!) or its Subsingleton instance.

I think Leibnitz equality is not really the same here, right? I use Leibnitz equality to mean roughly "indistinguishable things are equal", that is,

def LEq (x y : a) : Prop := forall P : a -> Prop, P x = Py

whereas extensional equality is something defined per-type that allows us to prove Eq, of the form forall x y : a, EQS x y -> x = y where EQS is some telescope of equalities of x and y in various contexts. This is usually related to/inspired by the intended semantics of the type in the standard Lean model, so funext captures set-theoretic extensional equality of functions, propext captures the fact that there's classically only really two propositions, ext for a structure captures that it's basically just a product, etc.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our comments crossed :-) I think the "all appropriate observations" is a fine way to describe it for this context.

:::tactic Lean.Elab.Tactic.Ext.applyExtTheorem
:::

The {tactic}`funext` tactic is a variant of {tactic}`ext` that specifically applies function extensionality.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should cross-reference the funext section of the quotients chapter

Comment thread Manual/Tactics/Reference.lean Outdated
jcreedcmu and others added 7 commits June 12, 2026 14:58
Co-authored-by: David Thrane Christiansen <david@davidchristiansen.dk>
Co-authored-by: David Thrane Christiansen <david@davidchristiansen.dk>
Co-authored-by: David Thrane Christiansen <david@davidchristiansen.dk>
Co-authored-by: David Thrane Christiansen <david@davidchristiansen.dk>
Co-authored-by: David Thrane Christiansen <david@davidchristiansen.dk>
Co-authored-by: David Thrane Christiansen <david@davidchristiansen.dk>
Co-authored-by: David Thrane Christiansen <david@davidchristiansen.dk>
:::

The {tactic}`cases` tactic performs case analysis on a term in the local context.
It decomposes the term according to the constructors of its inductive type, creating one subgoal for each constructor.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically it uses the registered cases eliminator, which for most types is just what you're specifying here. But it doesn't have to be - Nat has a special one that rephrases goals in terms of 0 and n + 1 instead of Nat.zero and Nat.succ n.

You can see this in action with this example:

def Bool' := { n : Nat // n = 0 ∨ n = 1 }

@[match_pattern] def Bool'.true : Bool' := ⟨1, by grind⟩
@[match_pattern] def Bool'.false : Bool' := ⟨0, by grind⟩ 
def Bool'.and : Bool' → Bool' → Bool'
  | .true, .true => .true
  | _, _ => .false

@[cases_eliminator]
def Bool'.cases {motive : Bool' → Sort u} (f : motive .false) (t : motive .true) (b : Bool') : motive b := by
  let ⟨n, ok⟩ := b
  match n with
  | 0 => exact f
  | 1 => exact t

example : Bool'.and x y = .true → x = .true ∧ y = .true := by
  cases x <;> cases y <;> simp [Bool'.and]

Bool' clearly has only one constructor, but cases x and cases y follow Bool'.cases in creating two cases. What's really going on is that it's creating one proof goal for each minor premise of the registered cases_eliminator, which by default is the one generated by the inductive machinery from the recursor but doesn't need to be.

```
:::

The {tactic}`rcases` tactic is a recursive version of {tactic}`cases` that destructs a hypothesis using pattern matching notation.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's important here that the rcases pattern language is not the same one used by match, let, etc.

:::

The {tactic}`induction` tactic performs mathematical induction.
Like {tactic}`cases`, it splits into cases, but it additionally provides an inductive hypothesis in each recursive case.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can also be rigged up to use a custom eliminator.


:::example "Induction on Natural Numbers"
Here we use induction to establish that zero is the identity for addition on the left.
The base case `zero` is closed by {tactic}`rfl`, and the successor case uses the inductive hypothesis `ih : 0 + n = n`:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is technically not following the rules you state above, because it's phrasing the goal in simp normal form instead of in terms of constructors. This is accomplished by having a custom induction_eliminator set up for Nat.

Perhaps List is a better example?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(it's good to at least mention the custom eliminator here for attentive readers)

%%%


The {tactic}`split` tactic splits the goal on a match expression or if-then-else, creating one subgoal per branch.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These subgoals include hypotheses that rule out the control-flow branches not taken, which means they preserve the "first match wins" semantics of match. This is crucial.

:::

:::example "Splitting a Match Expression"
The {tactic}`split` tactic creates one subgoal per branch, allowing each to be proved using the relevant hypotheses:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a first-match-wins example here. Something like:

def classify (n : Nat) : Option String :=
  match n with
  | 7 => some "seven"
  | 12 => some "twelve"
  | _ => none

Using spliton that will result in goals that include hypotheses that 7 and 12 were not matched. This is a big part of the value of split.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can do this but I don't know the canonical way to get names for these hypotheses. If split with is a thing, it seems to only be a mathlib thing. I can use next but I'm not stoked about using it so.

When splitting a case match, hypotheses are available that show that previous arms of the case did not match.
````lean
def classify (n : Nat) : Option String :=
  match n with
  | 7 => some "seven"
  | 12 => some "twelve"
  | _ => none

example (n : Nat) :
    classify n = some "seven" ∨ classify n = some "twelve" ∨
      (n ≠ 7 ∧ n ≠ 12) := by
  unfold classify
  split
  · left; rfl
  · right; left; rfl
  · right; right; next hx hy => refine ⟨hx, hy⟩

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can name them yourself with rename_i. But I think it's sufficient to state the proposition in the hypothesis without giving it a name here, like:

In the second case, split inserts the hypothesis n \ne 7 to indicate that the first case in classify was not taken. In the third case, it inserts both n \ne 7 and n \ne 12, indicating that neither the first nor the second case matched.

Comment thread Manual/Tactics/Reference.lean Outdated
```
:::

The {tactic}`by_cases` tactic splits the proof into two cases based on whether a proposition holds or not.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually we say "is true" for a proposition. "holds" isn't wrong, but sometimes it's useful to reserve that for metalanguage statements (ie judgments). In any case, consistency of terminology is useful.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, happy to change.

Comment thread Manual/Tactics/Reference.lean Outdated
:::

The {tactic}`by_cases` tactic splits the proof into two cases based on whether a proposition holds or not.
Always name the hypothesis with `h : P` syntax; without a name, Lean generates an inaccessible name (`h✝`) that cannot be referred to in the proof.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is that so bad? grind and other automation does just fine with inaccessible hypothesis names.

An xref to tactic hygiene here would be useful.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'm happy to soften the wording.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really understand why tactic hygiene is relevant here? This isn't challenging the relevance so much as confessing I'm missing information.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't name the hypothesis, then a name is assigned automatically. Due to hygiene, that name is inaccessible, so you can't refer to it later in the proof (but automation can still use it).

Comment thread Manual/Tactics/Reference.lean Outdated
Comment thread Manual/Tactics/Reference.lean
Comment thread Manual/Tactics/Reference.lean Outdated
tag := "tactic-ref-other"
%%%

The {tactic}`trivial` tactic tries a short list of simple tactics, including {tactic}`rfl`, {tactic}`assumption`, and {lean}`True.intro`, to close the goal.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tactic can be extended, right?

:::

:::example "Closing Easy Goals"
The {tactic}`trivial` tactic can close goals that are trivial from the point of view of propositional logic.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly speaking, propositional logic doesn't have a point of view :-)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nor does a mountain have one, but one may speak of the point of view of the peak, no? :) In any case that's my intent here. Feel free to suggest another wording if you'd like, I'm not too attached to it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"goals that are sufficiently easy to prove" perhaps?

Comment thread Manual/Tactics.lean
:::

:::example "Reverting Before Induction"
Reverting a hypothesis before {tactic}`induction` gives a stronger inductive hypothesis that quantifies over the reverted variable.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally I'd use generalizing here rather than a low-level revert-then-induction dance.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't ever use revert, so I didn't have a strong notion of how to make a super unimpeachably high-quality example.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. It is a kind of low-level thing that I expect I'd only really use in a tactic macro or something. I'd just leave the example alone.

Comment thread Manual/Tactics.lean Outdated
Comment thread Manual/Tactics.lean Outdated
jcreedcmu and others added 6 commits June 12, 2026 15:30
Co-authored-by: David Thrane Christiansen <david@davidchristiansen.dk>
Co-authored-by: David Thrane Christiansen <david@davidchristiansen.dk>
Co-authored-by: David Thrane Christiansen <david@davidchristiansen.dk>
Co-authored-by: David Thrane Christiansen <david@davidchristiansen.dk>
Co-authored-by: David Thrane Christiansen <david@davidchristiansen.dk>
@github-actions

Copy link
Copy Markdown
Contributor

Preview for this PR is ready! 🎉 (also as a proofreading version). built with commit a16ddd3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

HTML available HTML has been generated for this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants