Zulip Chat Archive
Stream: lean4
Topic: Check goal solved
Marcus Rossel (Jan 31 2024 at 12:19):
How does one check whether a given goal is solved after running a tactic?
It seems that getMainGoal
isn't the correct API for this. And also the goal's MVarId
changes after running a tactic:
import Lean
open Lean Meta Elab Tactic
elab "simp'" : tactic => do
logInfo m!"before: {(← getMainGoal).name}"
evalSimp <| ← `(simp)
logInfo m!"after: {(← getMainGoal).name}"
example : True = True := by
simp'
-- before: _unique.1990
-- no goals to be solved
example (a b : Nat): a + 0 = b := by
simp'
-- before: _unique.2015
-- after: _unique.2062
Notification Bot (Jan 31 2024 at 12:23):
Marcus Rossel has marked this topic as resolved.
Notification Bot (Jan 31 2024 at 12:23):
Marcus Rossel has marked this topic as unresolved.
Jannis Limperg (Jan 31 2024 at 16:25):
getGoals
to get the list of remaining goals, then check whether this is empty.
Jannis Limperg (Jan 31 2024 at 16:27):
When a tactic is run on a goal (represented by an MVarId
), it does not modify the goal. Rather, it 'solves' the goal (assigns the MVarId
) and returns zero or more remaining goals (with different MVarId
s).
Marcus Rossel (Jan 31 2024 at 17:00):
Thanks! From what I can tell that still means I need to do some additional checks when the tactic is run without focussing on a single goal:
import Lean
open Lean Meta Elab Tactic
elab "simp'" : tactic => do
logInfo m!"before: {(← getGoals).map MVarId.name}"
evalSimp <| ← `(simp)
logInfo m!"after: {(← getGoals).map MVarId.name}"
example : True = True := by
by_cases 0 = 0
· simp'
-- before: [_uniq.2065]
-- after: []
example : True = True := by
by_cases 0 = 0
simp'
-- before: [_uniq.2065, _uniq.2080]
-- after: [_uniq.2080]
example (a b : Nat): a + 0 = b := by
by_cases 0 = 0
simp'
-- before: [_uniq.2405, _uniq.2420]
-- after: [_uniq.2466, _uniq.2420]
Damiano Testa (Jan 31 2024 at 17:04):
One thing that confused me, is that changing the goal from a + 0 = ...
to a = ...
counts as having solved a goal and replaced it with a new one. In fact, it is not so easy to persistently maintain a notion of which new MVar is "the old one", unless you are careful about each step and you manage them yourself.
Damiano Testa (Jan 31 2024 at 17:06):
So, the outcome that you see now is what I've come to expect, but not what my initial intuition was.
Damiano Testa (Jan 31 2024 at 17:08):
E.g. 2466
is morally a witness that the goal has not been solved, but in reality the goal 2405
was solved and "out of nowhere" 2466
appeared.
Jannis Limperg (Jan 31 2024 at 17:16):
When you have multiple goals, you should indeed check whether all remaining goals were already present before you ran the tactic. Or you focus on the main goal only, i.e. setGoals [mainGoal]
before executing your tactic. Or you drop down to the MetaM
level, where each tactic has the form MVarId /- old goal -/ -> MetaM (List MVarId) /- new goals -/
.
Damiano Testa (Jan 31 2024 at 17:21):
Or if you are using MVarId.apply
, then you know that the list of goals that it returs is the list of "children" of your initial goal. This is a very robust way of proceeding, while calling evalSimp
(or something analogous) may mean that you lose control of what is going on.
Damiano Testa (Jan 31 2024 at 17:23):
I have the impression that the tag
system is intended as a quick-and-dirty way of doing some of the bookkeeping, but it can get cumbersome quickly.
Jannis Limperg (Jan 31 2024 at 17:46):
Afaik the tags are only visual aids for users. I wouldn't count on them being handled appropriately by all tactics (and it's not even clear what 'appropriately' means).
Damiano Testa (Jan 31 2024 at 17:48):
I agree: propagating tactics is much more consistent in Lean 4 than it was in Lean 3, but still not very robust.
However, for simple-minded implementations, I have manually set them to sort and retrieve side-goals: it was ok, but as soon as you have to do something a little involved, you are better off maintaining your own lists of MVars
.
Kyle Miller (Jan 31 2024 at 17:55):
A goal is "solved" if when you instantiate its metavariables, then it contains no more metavariables. You can approximate this by keeping track of the goal state, but you can also calculate this yourself.
import Lean
open Lean Meta Elab Tactic
elab "simp'" : tactic => do
let g := Expr.mvar (← getMainGoal)
logInfo m!"before: {g}"
evalTactic <| ← `(tactic| focus simp)
let g ← instantiateMVars g
logInfo m!"after: {g}"
if g.hasExprMVar then
logInfo m!"not solved"
else
logInfo m!"solved"
example : True = True := by
simp'
-- before: ?m.2205
-- after: of_eq_true (eq_self True)
-- solved
example (a b : Nat): a + 0 = b := by
simp'
-- before: ?m.2287
-- after: id ?m.2334
-- not solved
(I switched your code to use evalTactic
and focus
just to be sure the tactic is only applying to the top goal. That wasn't necessary for the experiment.)
Kyle Miller (Jan 31 2024 at 17:58):
Well, that's one notion of "solved". Maybe it's better to keep track of the goal states after all, since there might be some metavariables in your goal that are from the other not-yet-solved-for goals, and you might consider that to be "solved".
Marcus Rossel (Feb 01 2024 at 11:49):
Thanks for the help! I ended up going for the simple approach of using focus
, because that makes it simple to understand the output of getGoals
:
import Lean
open Lean Meta Elab Tactic
elab "simp'" : tactic => do
focus do
logInfo m!"before: {(← getGoals).map MVarId.name}"
evalSimp <| ← `(simp)
logInfo m!"after: {(← getGoals).map MVarId.name}"
example : True = True := by
by_cases 0 = 0
· simp'
-- before: [_uniq.2065]
-- after: []
example : True = True := by
by_cases 0 = 0
simp'
-- before: [_uniq.2230]
-- after: []
example (a b : Nat): a + 0 = b := by
by_cases 0 = 0
simp'
-- before: [_uniq.2405]
-- after: [_uniq.2466]
Damiano Testa (Feb 01 2024 at 14:18):
It may be that the first do
is superfluous (untested).
Last updated: May 02 2025 at 03:31 UTC