This PR adds a logic of stateful predicates SPred to Std.Do in order to
support reasoning about monadic programs. It comes with a dedicated
proof mode the tactics of which are accessible by importing
Std.Tactic.Do.
Co-authored-by: Sebastian Graf <sg@lean-fro.org>
This PR adds a logic of stateful predicates `SPred` to `Std.Do` in order
to support reasoning about monadic programs. It comes with a dedicated
proof mode the tactics of which are accessible by importing
`Std.Tactic.Do`.
Co-authored-by: Sebastian Graf <sg@lean-fro.org>
This PR adds configuration options for
`decide`/`decide!`/`native_decide` and refactors the tactics to be
frontends to the same backend. Adds a `+revert` option that cleans up
the local context and reverts all local variables the goal depends on,
along with indirect propositional hypotheses. Makes `native_decide` fail
at elaboration time on failure without sacrificing performance (the
decision procedure is still evaluated just once). Now `native_decide`
supports universe polymorphism.
Closes#2072
The `decide!` tactic is like `decide`, but when it tries reducing the
`Decidable` instance it uses kernel reduction rather than the
elaborator's reduction.
The kernel ignores transparency, so it can unfold all definitions (for
better or for worse). Furthermore, by using kernel reduction we can
cache the result as an auxiliary lemma — this is more efficient than
`decide`, which needs to reduce the instance twice: once in the
elaborator to check whether the tactic succeeds, and once again in the
kernel during final typechecking.
While RFC #5629 proposes a `decide!` that skips checking altogether
during elaboration, with this PR's `decide!` we can use `decide!` as
more-or-less a drop-in replacement for `decide`, since the tactic will
fail if kernel reduction fails.
This PR also includes two small fixes:
- `blameDecideReductionFailure` now uses `withIncRecDepth`.
- `Lean.Meta.zetaReduce` now instantiates metavariables while zeta
reducing.
Some profiling:
```lean
set_option maxRecDepth 2000
set_option trace.profiler true
set_option trace.profiler.threshold 0
theorem thm1 : 0 < 1 := by decide!
theorem thm1' : 0 < 1 := by decide
theorem thm2 : ∀ x < 400, x * x ≤ 160000 := by decide!
theorem thm2' : ∀ x < 400, x * x ≤ 160000 := by decide
/-
[Elab.command] [0.003655] theorem thm1 : 0 < 1 := by decide!
[Elab.command] [0.003164] theorem thm1' : 0 < 1 := by decide
[Elab.command] [0.133223] theorem thm2 : ∀ x < 400, x * x ≤ 160000 := by decide!
[Elab.command] [0.252310] theorem thm2' : ∀ x < 400, x * x ≤ 160000 := by decide
-/
```
---------
Co-authored-by: Joachim Breitner <mail@joachim-breitner.de>
This is the groundwork for a tactic index in generated documentation, as
there was in Lean 3. There are a few challenges to getting this to work
well in Lean 4:
* There's no natural notion of *tactic identity* - a tactic may be
specified by multiple syntax rules (e.g. the pattern-matching version of
`intro` is specified apart from the default version, but both are the
same from a user perspective)
* There's no natural notion of *tactic name* - here, we take the
pragmatic choice of using the first keyword atom in the tactic's syntax
specification, but this may need to be overridable someday.
* Tactics are extensible, but we don't want to allow arbitrary imports
to clobber existing tactic docstrings, which could become unpredictable
in practice.
For tactic identity, this PR introduces the notion of a *tactic
alternative*, which is a `syntax` specification that is really "the same
as" an existing tactic, but needs to be separate for technical reasons.
This provides a notion of tactic identity, which we can use as the basis
of a tactic index in generated documentation. Alternative forms of
tactics are specified using a new `@[tactic_alt IDENT]` attribute,
applied to the new tactic syntax. It is an error to declare a tactic
syntax rule to be an alternative of another one that is itself an
alternative. Documentation hovers now take alternatives into account,
and display the docs for the canonical name.
*Tactic tags*, created with the `register_tactic_tag` command, specify
tags that may be applied to tactics. This is intended to be used by
doc-gen and Verso. Tags may be applied using the `@[tactic_tag TAG1 TAG2
...]` attribute on a canonical tactic parser, which may be used in any
module to facilitate downstream projects introducing tags that apply to
pre-existing tactics. Tags may not be removed, but it's fine to
redundantly add them. The collection of tags, and the tactics to which
they're applied, can be seen using the `#print tactic tags` command.
*Extension documentation* provides a structured way to document
extensions to tactics. The resulting documentation is gathered into a
bulleted list at the bottom of the tactic's docstring. Extensions are
added using the `tactic_extension TAC` command. This can be used when
adding new interpretations of a tactic via `macro_rules`, when extending
some table or search index used by the tactic, or in any other way. It
is a command to facilitate its flexible use with various extension
mechanisms.
The `decide` tactic produces error messages that users find to be
obscure. Now:
1. If the `Decidable` instance reduces to `isFalse`, it reports that
`decide` failed because the proposition is false.
2. If the `Decidable` instance fails to reduce, it explains what
proposition it failed for, and it shows the reduced `Decidable` instance
rather than the `Decidable.decide` expression. That expression tends to
be less useful since it shows the unreduced `Decidable` argument (plus
it's a lot longer!)
Examples:
```lean
example : 1 ≠ 1 := by decide
/-
tactic 'decide' proved that the proposition
1 ≠ 1
is false
-/
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.
-/
```
When reporting the error, `decide` only shows the whnf of the
`Decidable` instance. In the future we could consider having it reduce
all decidable instances present in the term, which can help with
determining the cause of failure (this was explored in
8cede580690faa5ce18683f168838b08b372bacb).
@Kha The hack I posted at Zulip didn't really work
```
macro x:ident : tactic => throw $ Lean.Macro.Exception.error x s!"unknown tactic '{x.getId}'"
```
For example, we would still get a weird error message at
```
theorem ex3 (x : Nat) : x = x → x = x :=
have x = x by foo (aaa bbb) -- The error would be at `bbb`
fun h => h
```
There were other minor issues that could be fixed, but this one was bad.