Zulip Chat Archive
Stream: lean4
Topic: Kinds of goals closed by rw/simp only/simp
Sebastian Ullrich (May 04 2021 at 14:53):
We taught our students the tactics mentioned in the title among others today, and there was some confusion over which tactic closes which kind of goal implicitly. Intuitively we might think "rw
is weaker than simp only
is weaker than simp
", but this is not true in that regard:
rw
uses "cheap refl" in the end, that is,try (withReducible rfl)
simp only
triesTrue.intro
simp
also triesTrue.intro
only, but since there is a refl-like[simp]
theorem, it can close as much asrw
If we want to present rw
as a "manual simp
", in particular as a safe non-terminal tactic, it seems like it should not have this automatic final step. What do you think? Would it be bothersome in your experience to add an extra rfl
below a terminal rw
?
Markus Himmel (May 04 2021 at 14:57):
Maybe one could have rwr
which is like Lean 3 rwa
except that it does rfl
instead of assumption
?
Sebastian Ullrich (May 04 2021 at 15:00):
But rfl
is so much shorter to write out than asssumption
... :)
Sebastien Gouezel (May 04 2021 at 15:11):
I think Kevin disabled the rfl
at the end of a rewrite in his number theory game because his students could not understand what was going on. I agree that it is reasonable.
Patrick Massot (May 04 2021 at 15:13):
Sebastian, is the refl
tactic renamed rfl
in Lean4?
Patrick Massot (May 04 2021 at 15:13):
(I just want to be sure we're talking about the same thing).
Sebastian Ullrich (May 04 2021 at 15:19):
Ah, yes, it is. This one is not extensible (yet).
Kevin Buzzard (May 04 2021 at 15:45):
Lean 3's refl
tactic also closes P <-> P
goals, which Lean 4's rfl
doesn't. I disabled cheap rfl after rw in NNG not because I was against it, but because I made an active decision not to talk about definitional equality, so x + 0 = x
isn't refl in the eyes of the user (in fact we get occasional bug reports complaining that refl closes this when it shouldn't). I really like cheap refl after rw when I'm actually coding, but I would be just as happy to have rwr doing this (in fact perhaps rwr could try harder with refl if we go down this route, because I sometimes have to do rw, refl in Lean 3).
Mario Carneiro (May 05 2021 at 04:38):
I don't think rw
should lose the "cheap refl" at the end, or that it should be renamed to rwr
. But changing it to not do the cheap refl for educational purposes, like Kevin's NNG rw
, seems fine
Sebastian Ullrich (May 05 2021 at 07:13):
@Mario Carneiro I'm not sure I fully understood you, do you mean a different rw
should be used in teaching?
Mario Carneiro (May 05 2021 at 07:13):
yes, that's what NNG does
Mario Carneiro (May 05 2021 at 07:14):
once you think the students are comfortable with rw
enough that having implicit extra behavior isn't too magical you can take off the training wheels
Mario Carneiro (May 05 2021 at 07:16):
In fact I think the general technique of using curated and possibly simplified versions of lean tactics for the purposes of teaching is a very good idea. Lean is really complicated if you have to tackle all of it at once
Sebastian Ullrich (May 05 2021 at 07:22):
Yes, but ideally I would hope that can be achieved by simply not introducing the advanced tactics, or advances versions such as pattern intro
, in the beginning. I still think a clear "power hierarchy" of rw/simp only/simp would be a good design beneficial to both beginners and advanced users.
Mario Carneiro (May 05 2021 at 07:23):
In that case, perhaps simp only
should support cheap rfl
Mario Carneiro (May 05 2021 at 07:24):
it is a bit annoying to have to add eq_self_iff_true
anyway
Mario Carneiro (May 05 2021 at 07:25):
which has become more visible in mathlib since we started squeezing our simps
Sebastian Ullrich (May 05 2021 at 07:26):
Yes, that would also work. I think it would be fine for education.
Johan Commelin (May 05 2021 at 07:26):
We could have simp ONLY!! [foo, bar]
for the very restrictive version (-;
In case someone really doesn't want eq_self_iff_true
by default.
Sebastian Ullrich (May 05 2021 at 07:29):
Kevin Buzzard (May 05 2021 at 07:44):
We noticed that people were confused by rw
not closing refl
goals in NNG and then when they switched to real Lean suddenly it was, and at the time this was flagged as a potential downside of my rw hack. However NNG came from about a year of experimenting on mathematics undergraduates (this was the original version, and I watched a lot of people working through that stuff) and finding out what worked best for them, and the approach of hiding definitional equality seemed to win out.
The other hack in NNG tactics was that induction
in goals like 0 + x = x
would give you base case goals like 0 + zero = zero
which again was unsurprisingly found confusing, so there was a lot of rewriting nat.zero to 0 behind the scenes.
Sebastian Ullrich (May 05 2021 at 07:48):
@Kevin Buzzard Just to make sure we're on the same page, note that Lean 4's "cheap refl" would not close x + 0 = x
since Nat.add
is not reducible
Mario Carneiro (May 05 2021 at 07:50):
Lean 3's doesn't either btw
Mario Carneiro (May 05 2021 at 07:50):
example (x : ℕ) : x + 0 = x := by rw [] -- fail
Jasmin Blanchette (May 05 2021 at 07:50):
Sebastian Ullrich said:
Would it be bothersome in your experience to add an extra
rfl
below a terminalrw
?
For what it's worth, the corresponding tactic in Isabelle/HOL, subst
, does not automatically call rfl
, and that never seemed to bother anybody.
Kevin Buzzard (May 05 2021 at 07:50):
It surely closes 0 + 0 = 0, maybe that's what I was remembering
Mario Carneiro (May 05 2021 at 07:51):
so it does, that's odd
Kevin Buzzard (May 05 2021 at 07:51):
rw add_zero
-- "huh"?
Mario Carneiro (May 05 2021 at 07:52):
I think it would be cool if rw
used cheap (or even not cheap) refl at the end, if it is the last tactic in the branch. I don't know if the tactic parsing framework is able to determine this though
Kevin Buzzard (May 05 2021 at 07:52):
I remember now, even rw add_zero
doesn't close x + 0 = x
in NNG, that's the behaviour I wanted. People are so confused by rw
, they can't tell the difference between it and apply
, like some maths students can't tell the difference between and when they come to university, the terms are kind of used vaguely at school and sometimes interchangeably. [I am now reminded of a stroppy letter I wrote to my daughter's teacher when she was about 8]
Mario Carneiro (May 05 2021 at 07:53):
I think apply
is a vague word in mathematics though, it means "use this theorem somehow"
Mario Carneiro (May 05 2021 at 07:53):
lean's interpretation of apply
is a bit too constrained
Sebastian Ullrich (May 05 2021 at 07:59):
Mario Carneiro said:
I think it would be cool if
rw
used cheap (or even not cheap) refl at the end, if it is the last tactic in the branch. I don't know if the tactic parsing framework is able to determine this though
This sounds easy when you only consider branches, but much harder if you also want to propagate a sensible "terminal" flag through combinators. I believe the result would be more confusing than the status quo. A separate tactic name would be a cleaner solution.
Mario Carneiro (May 05 2021 at 08:01):
Indeed, I think it doesn't match the tactic architecture itself. If the number of goals returned by the tactic was enforced in the type system, like in HOL, then it would make more sense
Marc Huisinga (May 05 2021 at 08:03):
I don't think of rw
as a terminal tactic, so I'd be in favor of it not closing cheap rfl goals. Is an extra rfl
at the end really that inconvenient?
Marc Huisinga (May 05 2021 at 08:04):
Then again, I can see that it might be slightly annoying if then almost every proof case ends with rfl
, which I guess some might consider as noise.
Johan Commelin (May 05 2021 at 08:05):
I really like by rw [lem1, lem2]
in calc blocks, short and easy and fits on the same line as the calculation step.
Johan Commelin (May 05 2021 at 08:06):
If it becomes by rw [lem1, lem2]; rfl
those 5 extra chars will cause line overflow in some cases
Johan Commelin (May 05 2021 at 08:06):
I wouldn't mind avoiding that
Marc Huisinga (May 05 2021 at 08:07):
Johan Commelin said:
I really like
by rw [lem1, lem2]
in calc blocks, short and easy and fits on the same line as the calculation step.
This seems like something that calc
in particular could add, right?
Johan Commelin (May 05 2021 at 08:08):
Aah, I see. It wouldn't be very uniform though
Gabriel Ebner (May 05 2021 at 08:09):
It's not just calc block though, there are lots of lemmas like lemma foo : a = b := by rw [one, two, three]
. Requiring rfl
at the end is really cumbersome.
Sebastian Ullrich (May 05 2021 at 08:12):
@Johan Commelin Another aspect to consider, if a) simp
was much cheaper in general, and b) squeezing it into simp only
was only a Code Action away, do you think you would use rw
as often as today? Because I believe Isabelle users would probably default to simp
in these cases.
Sebastian Ullrich (May 05 2021 at 08:16):
It will be interesting to see how important/effective squeezing will be in Lean 4 in general. The discrimination trees fundamentally change the whole scalability issue.
Gabriel Ebner (May 05 2021 at 08:16):
rw
does a lot of things that simp doesn't: 1) it rewrites in the specified order (which is important if you go against the grain), 1b) it rewrites only once, 2) it can open extra subgoals.
Mario Carneiro (May 05 2021 at 08:17):
simp only
is also important for maintaining predictability/robustness in the face of new additions to the simp set
Gabriel Ebner (May 05 2021 at 08:17):
(Type-theory issues aside where rw
works but simp
doesn't.)
Mario Carneiro (May 05 2021 at 08:18):
(this is summarized in the "no nonterminal simp
" rule)
Sebastian Ullrich (May 05 2021 at 08:18):
Oh, nonterminal simp
s should definitely be squeezed of course
Sebastian Ullrich (May 05 2021 at 08:20):
@Gabriel Ebner Do you have a feeling for how often these features are used in terminal rw
s? Also not sure off the top of my mind if Lean 4 simp
can actually open new goals.
Mario Carneiro (May 05 2021 at 08:22):
I find rw
generally easier to control than simp
, for the reasons gabriel mentioned
Mario Carneiro (May 05 2021 at 08:22):
but I don't think there is a significant prejudice for one over the other where they both apply
Gabriel Ebner (May 05 2021 at 08:24):
Opening new goals typically does not happen with non-terminal rws. But still, having to write by rw Nat.sub_add_cancel; rfl; ...
is cumbersome and noisy.
Mario Carneiro (May 05 2021 at 08:26):
I wouldn't mind having rwr
for rewrite and then not-cheap refl, but I generally dislike tactics whose only purpose is to be two tactics in a row
Sebastian Ullrich (May 05 2021 at 08:26):
Probably terrible idea: instead of having tactics speculatively close goals, use an extensible set of tactics at done
(which is implicit at the end of a "terminal" tactic block) to do so instead. Then at least you really don't pay for the feature unless you use it.
Mario Carneiro (May 05 2021 at 08:27):
is this like my suggestion about doing it only for the last tactic in a block? How is the position detected / communicated?
Gabriel Ebner (May 05 2021 at 08:33):
Sebastian Ullrich said:
Also not sure off the top of my mind if Lean 4
simp
can actually open new goals.
This doesn't seem to work, even if I explicitly name the metavariable:
axiom Nat.sub_add_cancel {m n : Nat} (h : m ≤ n) : n - m + m = n
example (h : m < 10) : 10 - m + m = 10 := by
rw Nat.sub_add_cancel
exact Nat.leOfLt h
example (h : m < 10) : 10 - m + m = 10 := by
simp only [Nat.sub_add_cancel ?m]
-- simp makes no progress here
exact Nat.leOfLt h
Sebastian Ullrich (May 05 2021 at 08:38):
Mario Carneiro said:
is this like my suggestion about doing it only for the last tactic in a block? How is the position detected / communicated?
The block itself would do it, not the last tactic. Which is simpler and more robust (we only call the tactic set when we know we would otherwise fail with "unsolved goals" anyway), but means the last tactic can't influence what to try.
Mario Carneiro (May 05 2021 at 08:39):
ha, so we will see proofs by example : true := by
?
Sebastian Ullrich (May 05 2021 at 08:39):
Only if we allow that syntactically :)
Sebastian Ullrich (May 05 2021 at 08:39):
As I said, it's probably a terrible idea
Gabriel Ebner (May 05 2021 at 08:40):
I could get behind writing {}
instead of {refl}
.
Mario Carneiro (May 05 2021 at 08:40):
Having things that run on close isn't that unusual, it's supported in lean 3 as well, although it is only actually used in custom tactic blocks like smt_tactic
Johan Commelin (May 05 2021 at 08:41):
Mario Carneiro said:
ha, so we will see proofs by
example : true := by
?
How is this worse than example : false -> true.
?
Mario Carneiro (May 05 2021 at 08:41):
Did I mean to suggest I disliked the pattern? It's fine, just a bit odd
Mario Carneiro (May 05 2021 at 08:42):
in fact, since example : false -> true.
doesn't work anymore in lean 4 I will have to find a new favorite and that one looks like a candidate
Sebastien Gouezel (May 05 2021 at 09:34):
Having tactics run on the end of a proof is standard in Isabelle, and very convenient to close all remaining trivial goals.
Sebastian Ullrich (May 05 2021 at 09:56):
@Sebastien Gouezel If you don't specify an explicit one after qed
, it uses assumption
only, no?
Sebastien Gouezel (May 05 2021 at 10:15):
Yes, I think so -- but qed (auto)
is a very standard idiom.
Sebastian Ullrich (May 05 2021 at 10:18):
Since there is no token at the end of a proof block anymore anyway, we can add qed
as a macro for allGoals
in Lean 4 :grinning_face_with_smiling_eyes:
Mac (May 05 2021 at 13:59):
Just to throw in my own two cents: I like the idea of rwr
. I do have qualms with auto-closing "trivial' goals at the end of a proof without some kind of marker (such as a qed
) that you are doing so though. My design philosophy is to minimize implicit magic as much as possible.
Jeremy Avigad (May 05 2021 at 17:38):
IIRC, SSReflect has a simple done
tactic that you can invoke in places by writing //
. So for example you can write
rewrite foo bar baz //
and the done
would be invoked at the //
. Again IIRC, it was a try
tactic, so there was no harm if it failed. rewrite
would sometimes open up new subgoals (with conditional rewrites), and this was a way you could close them off and then keep going after the //
.
(Georges Gonthier was a master of optimizing the number of things that can be accomplished with a single 80-character line of SSReflect code.)
Jeremy Avigad (May 05 2021 at 17:40):
Docs:
https://coq.inria.fr/refman/proof-engine/ssreflect-proof-language.html#an-extended-rewrite-tactic
https://coq.inria.fr/refman/proof-engine/ssreflect-proof-language.html#simplification-items
Last updated: Dec 20 2023 at 11:08 UTC