Zulip Chat Archive
Stream: mathlib4
Topic: Understanding tauto.lean (mathlib3)
Thomas Murrills (Oct 08 2022 at 23:10):
I'm trying to understand tauto.lean
in mathlib3 (just as a way to learn how tactic-writing works), and I thought it might be worth asking a couple questions as to what's going on. I think understand what "should" be going on for the most part mathematically, but the meaning and usage of some lean operations is throwing me. Figured I'd gather them all here instead of creating a bunch of topics! (Unless that's frowned upon for some reason, in which case I'll make a bunch of topics. :smile: )
Thomas Murrills (Oct 08 2022 at 23:12):
First, what is local_pp_name
is meant to do (or where is documentation for this)? The source code doesn't seem to be commented, and I can't find it in the reference manual.
Thomas Murrills (Oct 08 2022 at 23:13):
Equivalently, what would the analogue in Lean 4 be? Or would a totally different approach be used? Here, local_pp_name
is used like this:
...
do h ← get_local h.local_pp_name,
e ← infer_type h,
match e with
| `(¬ _ = _) := replace h.local_pp_name ``(mt iff.to_eq %%h)
| `(_ ≠ _) := replace h.local_pp_name ``(mt iff.to_eq %%h)
| ...
Mario Carneiro (Oct 09 2022 at 00:08):
local_pp_name
returns the pretty-printing name (the thing users see) for a local constant (now known as an fvar)
Thomas Murrills (Oct 09 2022 at 00:08):
My current guess is h.local_pp_name
is equivalent to h.userName
where h
might be provided by something like for h in ← getLCtx
. But it seems a bit unsafe to me to use replace
with what I understand to be a user-facing name to replace hypotheses after doing syntax matching. (Though I'm not sure if that is indeed what's happening here.)
Is there a better pattern for modifying a hypothesis in lean 4, in general?
Mario Carneiro (Oct 09 2022 at 00:09):
what does replace
do in this code?
Mario Carneiro (Oct 09 2022 at 00:09):
it looks like a local function
Thomas Murrills (Oct 09 2022 at 00:10):
here's how the function starts—it's the first in the file
/--
find all assumptions of the shape `¬ (p ∧ q)` or `¬ (p ∨ q)` and
replace them using de Morgan's law.
-/
meta def distrib_not : tactic unit :=
do hs ← local_context,
hs.for_each $ λ h,
all_goals' $
iterate_at_most' 3 $
do h ← get_local h.local_pp_name,
e ← infer_type h,
match e with
| `(¬ _ = _) := replace h.local_pp_name ``(mt iff.to_eq %%h)
| `(_ ≠ _) := replace h.local_pp_name ``(mt iff.to_eq %%h)
| ...
Thomas Murrills (Oct 09 2022 at 00:10):
-
Mario Carneiro (Oct 09 2022 at 00:11):
I mean, where is the replace
function defined?
Mario Carneiro (Oct 09 2022 at 00:11):
it might not mean what you think
Thomas Murrills (Oct 09 2022 at 00:11):
VS Code says it's tactic.replace
Mario Carneiro (Oct 09 2022 at 00:11):
which is?
Thomas Murrills (Oct 09 2022 at 00:12):
"replace h p elaborates the pexpr p, clears the existing hypothesis named h from the local context, and adds a new hypothesis named h. The type of this hypothesis is the type of p. Fails if there is nothing named h in the local context."
Thomas Murrills (Oct 09 2022 at 00:12):
which seems to me like it's finding the local hypothesis by the pretty-printed name
Thomas Murrills (Oct 09 2022 at 00:12):
which sounds unsafe
Mario Carneiro (Oct 09 2022 at 00:12):
It's true, that is imprecise
Mario Carneiro (Oct 09 2022 at 00:13):
most likely it's written like that to fit the signature of tactic.replace
which is defined in core or something
Thomas Murrills (Oct 09 2022 at 00:13):
hmmm
Mario Carneiro (Oct 09 2022 at 00:13):
that kind of thing is reasonable for user-facing tactics since the name is coming from something the user wrote anyway
Mario Carneiro (Oct 09 2022 at 00:15):
I should mention that I'm not really happy with the original lean 3 implementation of tauto, so I'm okay with it getting a rewrite on the way to lean 4 as long as it does basically similar things
Mario Carneiro (Oct 09 2022 at 00:15):
although I realize that's a bit much to ask of someone who doesn't have a lot of experience writing lean tactics
Thomas Murrills (Oct 09 2022 at 00:15):
I see, cool! In general, is there better functionality in Lean 4 for modifying a hypothesis (seeing as we're just iterating over all of them)?
Mario Carneiro (Oct 09 2022 at 00:16):
You shouldn't "modify" hypotheses for the most part
Mario Carneiro (Oct 09 2022 at 00:16):
instead, you revert them and intro the modified versions
Mario Carneiro (Oct 09 2022 at 00:17):
it is important for lean's internal coherence that an fvar only ever has a single type in a single local context
Thomas Murrills (Oct 09 2022 at 00:17):
although I realize that's a bit much to ask of someone who doesn't have a lot of experience writing lean tactics
I mean, I'm willing to try! I just wouldn't want to prevent other people with more experience from potentially making better choices, lol
Mario Carneiro (Oct 09 2022 at 00:18):
Well there's always review time :P
Mario Carneiro (Oct 09 2022 at 00:18):
but it's great to hear you want to try it, I'll help however I can
Thomas Murrills (Oct 09 2022 at 00:19):
ok, that sounds great! :)
Thomas Murrills (Oct 09 2022 at 00:20):
instead, you revert them and intro the modified versions
hmmm... so would we start with something like for h in <- getLCtx
at all, as I initially thought, then? revert
ing then intro
ing seems to suggest we do a bunch of reversions then inspect the type of the goal, then do a bunch of intros based on that
Thomas Murrills (Oct 09 2022 at 00:21):
however, in this case it seems we might only want to revert a hypothesis based on its type in the first place. so maybe the for
still works (thinking out loud)
Mario Carneiro (Oct 09 2022 at 00:22):
The usual pattern is that you say "revert h" and it reverts h and also h' which depended on h, and it gives you an array which you later pass to introN to re-intro them after rewriting
Thomas Murrills (Oct 09 2022 at 00:22):
ah, ok
Thomas Murrills (Oct 09 2022 at 22:00):
Ok, general question: I'm not totally sure what tauto!
is meant to do in opposition to tauto
; according to the documentation "The variants tautology!
and tauto!
use the law of excluded middle", but tauto
seems to use LEM anyway, as example (p : Prop) : p ∨ ¬ p := by tauto
works. Is there something I'm missing? (I see that tauto!
runs classical
first, but I can't quite tell what meaningful effect that has.)
Mario Carneiro (Oct 09 2022 at 22:35):
It's kind of broken, don't worry about it. I wrote itauto
exactly because tauto
wasn't living up to its LEM-free claim. I think you should just implement one version of tauto
and use classical logic.
Kevin Buzzard (Oct 09 2022 at 22:40):
Yes, mathlib is designed to be a classical library and the main issue at stake here is porting it to lean 4 so classical assumptions are fine.
Mario Carneiro (Oct 09 2022 at 22:41):
it is easy enough to translate both tauto
and tauto!
to tauto
in mathport, so we can remove the option entirely
Thomas Murrills (Oct 09 2022 at 23:04):
Ok, sounds good! I'm thinking about how we might improve the functionality tauto
in general now...
Thomas Murrills (Oct 09 2022 at 23:05):
For example, it seems like currently it applies solve_by_elim
at the end to address ∃
expressions, but doesn't address tautologies within those. e.g. example (p q : Prop) (hq : q) : ∃ (hq : q), p → p := by tauto
fails. It seems easy enough to make it recursive—would it be alright to make it succeed here?
Mario Carneiro (Oct 09 2022 at 23:12):
yes
Mario Carneiro (Oct 09 2022 at 23:13):
as long as you can do the test cases, it's okay to do more
Thomas Murrills (Oct 09 2022 at 23:14):
great! where are the test cases located, by the way?
Mario Carneiro (Oct 09 2022 at 23:15):
test/tauto.lean
in mathlib
Thomas Murrills (Oct 09 2022 at 23:15):
oop. of course
Thomas Murrills (Oct 09 2022 at 23:15):
(I was looking in testing
by mistake)
Thomas Murrills (Oct 09 2022 at 23:26):
also, thinking of improving the functionality: it seems strange to me that tauto
is exclusively a finishing tactic when it produces a bunch of goals. wouldn't it be useful for a user to be able to carry on from that point, and supply the remaining goals after tauto
does some work? (or is that named/would that be named something else?)
Mario Carneiro (Oct 09 2022 at 23:27):
can you describe the goals that are generated in a clear way, such that the user can predict them?
Thomas Murrills (Oct 09 2022 at 23:28):
hmmm
Mario Carneiro (Oct 09 2022 at 23:28):
If you just do a bunch of mumbo jumbo and end up somewhere and hand control back to the user, that's not very robust to tactic changes
Thomas Murrills (Oct 09 2022 at 23:29):
I see, makes sense
Mario Carneiro (Oct 09 2022 at 23:29):
The standard is a lot higher on non-"finishing tactics" to produce subgoals deterministically
Mario Carneiro (Oct 09 2022 at 23:30):
so most of the really high powered and heuristic based tactics are either finishing tactics by construction or convention
Thomas Murrills (Oct 09 2022 at 23:41):
that's a good convention! (but hmmm...and, it's not necessary for reimplementing tauto
, but...I wonder if there exists a sort of canonical "tautological normalization" one can perform which reduces a proposition "as much as possible" in a certain mathematically-unique way. I kind of doubt that there does, but it'd be neat! After all, some of the goals that tauto
produces anyway are pretty intelligible...but that's a generalization for later :) )
Alex J. Best (Oct 10 2022 at 09:36):
One thing that might be useful is rather than setting new subgoals (which may indeed be a bit random), to print some sufficient facts (when tauto
fails but does reduce the problem a bit) that the user can have :=
into context above, so that tauto
will then finish.
Thomas Murrills (Oct 17 2022 at 22:44):
Okay, finally feeling well enough to start working on tauto
again! I'm still working through the existing file to see what it does.
Thomas Murrills (Oct 17 2022 at 22:45):
First question: consider the definition of add_symm_proof
.
/--
If there exists a symmetry lemma that can be applied to the hypothesis `e`,
store it.
-/
meta def add_symm_proof (r : tauto_state) (e : expr) : tactic (expr × expr) :=
do env ← get_env,
let rel := e.get_app_fn.const_name,
some symm ← pure $ environment.symm_for env rel
| add_refl r e,
(do e' ← mk_meta_var `(Prop),
iff_t ← to_expr ``(%%e = %%e'),
(_,p) ← solve_aux iff_t
(applyc `iff.to_eq ; () <$ split ; applyc symm),
e' ← instantiate_mvars e',
m ← read_ref r,
write_ref r $ (m.insert e (e',p)).insert e' none,
return (e',p) )
<|> add_refl r e
Thomas Murrills (Oct 17 2022 at 22:46):
What exactly does () <$ split
do, for one? I understand what <$
does in isolation, and have an idea what split
does, but I'm not sure how they're used together in context with ()
(the inhabitant of unit
, as in tactic unit
, somehow, maybe?).
Mario Carneiro (Oct 17 2022 at 23:01):
() <$ tac
is an idiom similar to discard tac
or _ <- tac
, it runs tac
and then returns ()
instead of whatever the tactic was going to return
Thomas Murrills (Oct 17 2022 at 23:33):
ah, ok, thanks!
Last updated: Dec 20 2023 at 11:08 UTC