Zulip Chat Archive
Stream: lean4
Topic: Surprising behavior of `withTacticInfoContext`
Sebastian Zimmer (Nov 14 2023 at 22:15):
I just spent a few hours debugging a tactic and it turns out the cause is that withTacticInfoContext
implicitly prunes the solved goals after it has completed. It does this because getUnsolvedGoals
calls pruneSolvedGoals
as a side effect.
I found this extremely surprising. Now that I know why it happens it is easy to work around, but I doubt I'm the last guy who will run into this issue. If it is intended that getUnsolvedGoals
should have this side effect, then I would strongly suggest that it should be renamed.
Jannis Limperg (Nov 14 2023 at 22:55):
The fact that there can be assigned mvars in the goal list is just a performance optimisation -- ideally any assignment would immediately remove the assigned mvar (since it's not a goal anymore). So you should never rely on assigned mvars staying in the goal list, and you should always prune the goal list before reading it, which is why getUnsolvedGoals
does just that. I'm curious why your use case would require assigned mvars to be preserved as 'goals'.
Sebastian Zimmer (Nov 14 2023 at 22:57):
I was solving the goal and then doing replaceMainGoal
afterwards, but because withTacticInfoContext was being used the goal was being removed from the list too early and the wrong goal was replaced.
Jannis Limperg (Nov 14 2023 at 23:02):
I see how that might be confusing. I wouldn't object to renaming getUnsolvedGoals
to pruneAndGetUnsolvedGoal
or something, but I doubt you're going to get this past Leo.
Sebastian Zimmer (Nov 14 2023 at 23:06):
If the tactic author is supposed to handle solved goals nondeterministically going away then I don't see how you could ever safely use replaceMainGoal
Maybe instead of replaceMainGoal
there should be a function which prepends to the list of goals and then prunes them.
Kyle Miller (Nov 14 2023 at 23:09):
I find replaceMainGoal
to be sketchy, and you should only use it if you're sure of the current state of the goal list.
There are functions like docs#Lean.Elab.Tactic.liftMetaTactic for running a MetaM tactic on the main goal and prepending the list.
Kyle Miller (Nov 14 2023 at 23:11):
It seems like it'd make sense to have a popMainGoal
function and a prependGoals
function (like appendGoals
)
def prependGoals (mvarIds : List MVarId) : TacticM Unit :=
modify fun s => { s with goals := mvarIds ++ s.goals }
/-- Returns `getMainGoal` and removes it from the goal list. -/
def popMainGoal : TacticM MVarId := do
let (mvarId :: mvarIds') ← getUnsolvedGoals | throwNoGoalsToBeSolved
modify fun _ => { goals := mvarIds' }
return mvarId
Sebastian Zimmer (Nov 14 2023 at 23:17):
There are functions like docs#Lean.Elab.Tactic.liftMetaTactic for running a MetaM tactic on the main goal and prepending the list.
Glancing at the code I don't think that avoids the issue I was having. If you do
liftMetaTactic do
withTacticInfoContext [...] do
-- assign the goal to something
I'm pretty sure this fails since the assigned goal gets pruned before liftMetaTacticAux
calls replaceMainGoal
It seems like it'd make sense to have a popMainGoal function and a prependGoals function (like appendGoals)
This makes sense to me and avoids these issues
Kyle Miller (Nov 14 2023 at 23:21):
The trick is that liftMetaTactic
only takes a MetaM
, so it can't modify the goals list. (In particular you can't use withTacticInfoContext
in MetaM
)
Sebastian Zimmer (Nov 14 2023 at 23:21):
Ah I see
Scott Morrison (Nov 15 2023 at 00:06):
Doing as much as possible down in MetaM is usually a good idea. I try and escape TacticM at the first available opportunity. :-)
Last updated: Dec 20 2023 at 11:08 UTC