Zulip Chat Archive
Stream: mathlib4
Topic: !4#3758 – rfl refactor & fix
Thomas Murrills (May 02 2023 at 01:26):
I want to check something before I go forward. The following is the for
loop with surrounding commands in rfl (goal : MVarId) : MetaM Unit
in the present state of the PR:
...
let s ← saveState
for lem in ← (reflExt.getState (← getEnv)).getMatch rel do
try
let gs ← goal.apply (← mkConstWithFreshMVarLevels lem)
if gs.isEmpty then return () else
logError <| MessageData.tagged `Tactic.unsolvedGoals <| m!"unsolved goals\n
{goalsToMessageData gs}"
catch e =>
s.restore
throw e
throwError "rfl failed, no lemma with @[refl] applies"
The following is the original, in TacticM
:
...
let s ← saveState
for lem in ← (reflExt.getState (← getEnv)).getMatch rel do
try
liftMetaTactic (·.apply (← mkConstWithFreshMVarLevels lem))
return
catch e =>
s.restore
throw e
throwError "rfl failed, no lemma with @[refl] applies"
Questions I have:
-
Why do we
throw e
from thecatch
block? Does this exit thefor
loop early? It seems not to. If not, whythrow
it at all, since we don't catch and use it? -
Should we restore the state if
apply
fails to close the goal? That is, should we exit to the catch with
...
let gs ← goal.apply (← mkConstWithFreshMVarLevels lem)
if gs.isEmpty then return () else
logError <| MessageData.tagged `Tactic.unsolvedGoals <| m!"unsolved goals\n
{goalsToMessageData gs}"
failure
Though, does try
not automatically restore the state? (I was under the impression it did...)
Mario Carneiro (May 02 2023 at 01:28):
- Yes, it exits early. Why throw? Because we want to report the apply failure as the error message, not "no
@[refl]
lemma applies"
Mario Carneiro (May 02 2023 at 01:31):
- No we should not restore the state, the state at an error should reflect whatever was happening at the time as an aid to debugging. No,
try
does not automatically restore the state.<|>
does.
Thomas Murrills (May 02 2023 at 01:32):
Ah, ok. But, re (1)...why would we want to exit the for loop early if a single lemma in the loop failed to be applied? Wouldn't we want to try the rest and continue the loop?
Thomas Murrills (May 02 2023 at 01:34):
I guess we're betting on the getMatch
meaning that apply
always succeeds (in MetaM
), and if it doesn't, tell us.
Mario Carneiro (May 02 2023 at 01:35):
that's a fair point. Both throw
and return
mean we will exit early, so the loop is not a loop
Mario Carneiro (May 02 2023 at 01:36):
better would be to stash the first error and report it if we fall out of the loop
Thomas Murrills (May 02 2023 at 01:37):
Makes sense—just the first one, or all of them in e.g. an Array?
Mario Carneiro (May 02 2023 at 01:37):
just the first
Thomas Murrills (May 02 2023 at 01:37):
Ok, cool!
Mario Carneiro (May 02 2023 at 01:38):
you can't report multiple errors like that
Mario Carneiro (May 02 2023 at 01:39):
if this mattered more, we might want to consider putting in those structured trace messages so that you can get a tree of emoji explosions and see all the errors
Thomas Murrills (May 02 2023 at 01:41):
ah, gotcha! So wait, is reporting this kind of error something we'd want to log or throw
? I can see logError
doesn't fail/except out, but I'm not sure when we want to use each
Thomas Murrills (May 02 2023 at 01:42):
Or, is logging even compatible with Exception
s?
Mario Carneiro (May 02 2023 at 01:46):
throw
means early exit. Use it if you can't complete the task given by the type signature
Mario Carneiro (May 02 2023 at 01:48):
logging and Exceptions are mostly orthogonal. logError
means put a red squiggle, throw
means early exit until you get caught by something, like a tactic control flow operator like repeat
/ try
, or the default handler that turns the exception into a logError
otherwise
Mario Carneiro (May 02 2023 at 01:50):
actually thinking about control flow manipulation is probably the easiest way to understand things here. If you use logError
and continue on failure, then if the user uses try rfl
when it doesn't apply they will get a red squiggle on rfl
anyway
Thomas Murrills (May 02 2023 at 01:51):
Ah, ok—thanks for clearing that up, I didn't know how red squiggles were born!
Thomas Murrills (May 02 2023 at 01:56):
So I guess the stashing-exception approach has us manipulate control flow when the loop ends, but varies why (i.e. "because the loop ended" or "because the loop ended and some apply
failed in the middle of it")
Thomas Murrills (May 02 2023 at 01:58):
re (2): good to have the try
behavior cleared up! :) Having the state reflect what was happening at the error suggests that if we stash the first exception and fall out of the loop, we actually want to save the state when the exception is encountered, then restore it before throwing that exception (if we do)
Thomas Murrills (May 02 2023 at 02:01):
Is there any reason to restore the MetaM
state before each round of the for
loop just under normal (successful) circumstances? Tbh I'm a little unclear on what state MetaM
carries—mvar assignments, for one, I'm guessing? Seems like this gets by without worrying about that, though...and I'm a little unclear on how.
Mario Carneiro (May 02 2023 at 02:01):
In both cases, rfl
has "failed". (More precisely, rfl
fails whenever no @[refl]
lemma applies.) The only difference is the error message we produce
Mario Carneiro (May 02 2023 at 02:02):
the mvar assignments are the important thing. If the apply fails we want to roll back any partial unification that happened when applying the next lemma
Thomas Murrills (May 02 2023 at 02:03):
That 's what I would expect! But, why does it seem to work if we don't do that?
Mario Carneiro (May 02 2023 at 02:03):
because the loop wasn't a loop?
Mario Carneiro (May 02 2023 at 02:04):
I don't think the multiple refl lemma case has been tested at all
Mario Carneiro (May 02 2023 at 02:04):
the discrimination tree is good enough to avoid needing it most of the time
Thomas Murrills (May 02 2023 at 02:05):
ahh ok! makes sense.
Thomas Murrills (May 02 2023 at 02:06):
Ok, I think that's sufficient for me to fix it up! :) ty!
Thomas Murrills (May 02 2023 at 06:08):
One last thing: is the raw throw e
in the case of an apply
exception what we want? Seems like it might be mysterious. Or do we want something more descriptive, involving throwError "... {e.toMessageData}"
? (Currently it just throw
s.)
Mario Carneiro (May 02 2023 at 12:26):
the error message from apply is pretty okay, something like couldn't match 2 + 2 = 5 with ?a <= ?a
, I don't see much reason to put a header on that
Thomas Murrills (May 02 2023 at 19:13):
Ok, so the benchmark says:
There were significant changes against commit 42f68ca:
Benchmark Metric Change
====================================================
- ~Mathlib.Tactic.Relation.Rfl instructions 13.9%
in red—does this mean that that file took 13.9% longer (which is fine, since it’s very quick in absolute time), or that instructions(?) “from” that file took longer?
Sebastian Ullrich (May 02 2023 at 19:15):
There were 13.9% more CPU instructions, which essentially means it got 13.9% slower
Thomas Murrills (May 02 2023 at 19:16):
But, just while processing that file, or overall, because of that file?
Thomas Murrills (May 02 2023 at 19:24):
(Well, not quite “overall”, but what I mean is—are these instructions that will be reused when declarations in Mathlib.Tactic.Relation.Rfl
are used in other files?)
Sebastian Ullrich (May 02 2023 at 19:29):
While processing that specific file
Thomas Murrills (May 02 2023 at 19:30):
Ok, ty! :)
Last updated: Dec 20 2023 at 11:08 UTC