Zulip Chat Archive
Stream: mathlib4
Topic: Announcement: new `push` tactic
Jovan Gerbscheid (Sep 28 2025 at 12:17):
The push and pull tactics were recently merged into Mathlib. They are a generalization of push_neg, so now push_neg is a macro for push Not. The push tactic is extensible, so we need to tag a bunch of lemmas with @[push] in order to start using it. This includes tags to extend push_neg. I made #29000 to add a bunch of these tags, but it got a bit large, so I'm splitting it into several PRs to make it easier to review. Would someone like to review them?
Jovan Gerbscheid (Sep 28 2025 at 12:17):
Annotations for Real.log: #30039
Jovan Gerbscheid (Sep 28 2025 at 12:18):
Annotations for fun: #30038
Jovan Gerbscheid (Sep 28 2025 at 12:18):
Annotations for membership in Set, Finset and Multiset: #30042
Jovan Gerbscheid (Sep 28 2025 at 12:19):
Annotations for push_neg for Empty/Subsingleton/Finite/Countable: #30051
Jovan Gerbscheid (Sep 28 2025 at 12:34):
When working on these PRs, I would have liked the following features:
- If the goal is
IsEmpty AorSubsingleton A, it would be nice to be able to useby_contra!with anrintropattern. absurdshould have anabsurd!version that callspush_negby_casesshould have aby_cases!version that callspush_negon the second sub-goalby_contraandby_contra!behave differently when no argument is given. One of them introduces a hypothesisthis, and the other gives an inaccessible name. Should it always bethis?- When the goal is
X ⊆ Y, I would like a version ofintrothat automatically callspush _ ∈ _on the new goal, so that if the goal is for example{x | P x} ⊆ {x | Q x}, I want it to giveP x → Q x. Maybe it should be calledmem_introorintro_mem?
Do you agree with these suggestions?
Yaël Dillies (Sep 28 2025 at 12:40):
Jovan Gerbscheid said:
now
push_negis not a macro forpush Not
Typo?
Patrick Massot (Sep 28 2025 at 13:42):
Did you made sure to keep everything that teachers use? Originally push_neg was a pure teaching tactic.
Patrick Massot (Sep 28 2025 at 13:43):
And verbose Lean currently has the awful https://github.com/PatrickMassot/verbose-lean4/blob/bd3b236511d9a8929791f20f775e98a9ef81697c/Verbose/Tactics/Common.lean#L563-L625
Jovan Gerbscheid (Sep 28 2025 at 14:10):
@Patrick Massot Yes, the option push_neg.use_distrib is still supported. The only behaviour that has changed is that it looks through reducible constants better:
import Mathlib
axiom Bar : Prop
abbrev Foo := ¬ Bar
#push_neg ¬ Foo -- Bar
Robert Maxton (Sep 29 2025 at 00:12):
@Jovan Gerbscheid
I would suggest that by_cases! just calls push_neg on both goals; sometimes it is convenient for one reason or another to put the negated version first. It would also be nice to turn ¬ Set.Nonempty into = ∅; bonus points if it also supposes rcases patterns so you can immediately case on the equality, but that might be beyond the scope of push
Robert Maxton (Sep 29 2025 at 00:13):
otherwise, yes, those all look good to me
Jovan Gerbscheid (Sep 29 2025 at 17:02):
If you want to get the negated version first, for example by_cases! h : ¬ p, then in the first goal we get h : ¬ p, and in the second goal h : p. The tactic doesn't actually need to push_neg in the first goal, only in the second goal. Also, I think it might be more readable to write by_cases h : p; swap.
Before my work, push_neg did already turn ¬ Set.Nonempty into = ∅. But I will add tags for e.g. the same rule for Finset.
I agree it would be nice to support rcases patterns in by_cases! (and by_cases), but I'm not sure what the syntax should be, given that the same variable name refers to two different introduced variables. Maybe we can have syntax by_cases! (rfl | h) : a = ∅.
Jovan Gerbscheid (Sep 29 2025 at 17:02):
Here's my PR for by_cases!: #30073
Jovan Gerbscheid (Oct 20 2025 at 18:41):
I've now split the tactic implementation of by_cases! into a separate PR: #30730
Last updated: Dec 20 2025 at 21:32 UTC