Zulip Chat Archive
Stream: mathlib4
Topic: Announcement: New `grw` tactic
Jovan Gerbscheid (Jun 13 2025 at 15:43):
This is an announcement that the grw tactic is now available in mathlib. It allows rewriting with any relation, usually a transitive relation, if the relevant lemmas are tagged with @[gcongr]. Some examples are:
example (h₁ : a < b) (h₂ : b ≤ c) : a + d ≤ c + d := by
grw [h₁, h₂]
example (h : a ≡ b [ZMOD n]) : a ^ 2 ≡ b ^ 2 [ZMOD n] := by
grw [h]
example : (h₁ : a ∣ b) (h₂ : p ∣ a * d) : p ∣ b * d := by
grw [← h₁]
exact h₂
grw has been designed to have the same syntax and the same features as rw. This means that there is also an nth_grw tactic. And it means that grw cannot rewrite bound variables.
The original PR was opened 2 years ago. I took up this task one week ago, and thanks to the in-person review by @Floris van Doorn, it got merged this week.
I'm making this thread to list some more features that I'd like to have for gcongr and grw (and some of which I'll try to do soon). But I'd also appreciate feedback on the tactic. Although many lemmas have already been tagged with @[gcongr], I'm sure there will be new use cases for which the lemmas haven't been tagged.
Floris van Doorn (Jun 13 2025 at 15:57):
(I think there is a typo in your last example)
Terence Tao (Jun 13 2025 at 16:04):
I remember this tactic being proposed during the PFR project (where it would have come in handy). Great to see it finally over the finish line!
Jovan Gerbscheid (Jun 13 2025 at 16:10):
@Floris van Doorn, thanks for pointing that out. I'll have to fix the doc-string of grw. But now I'm actually not sure which relation should be used inside the RHS of a divisibility:
example (p a b : Int) : p ∣ a → p ∣ b := by
gcongr
-- ⊢ a ≡ b [ZMOD p]
-- ⊢ a ∣ b
Which of the two possible goals do we expect?
A solution allowing both would be to get a ∣ b if the goal is p ∣ a → p ∣ b, and get a ≡ b [ZMOD p] if the goal is p ∣ a ↔ p ∣ b.
And grw could implement the heuristic that if the relation used for rewriting is symmetric, then it runs gcongr on an ↔ goal instead of an → goal.
Andrew Yang (Jun 13 2025 at 17:30):
Imagine how many more tactics we would have in mathlib if we just lock Jovan and Floris in a dungeon for a month.
Filippo A. E. Nuccio (Jun 13 2025 at 18:18):
Is there a way to obtain a list of all lemmas marked @[gcongr]?
Aaron Liu (Jun 13 2025 at 18:28):
import Mathlib
open Lean
run_meta do
let env ← getEnv
let map := Mathlib.Tactic.GCongr.gcongrExt.getState env
logInfo (map.toList.flatMap fun k => k.snd.toList.map fun u => MessageData.ofConstName u.declName)
Aaron Liu (Jun 13 2025 at 18:29):
There's 610 of them right now and two duplicates for some reason so actually only 608 unique ones
Aaron Liu (Jun 13 2025 at 18:34):
The duplicates are docs#List.Sublist.drop and docs#Nat.succ_le_succ
Filippo A. E. Nuccio (Jun 13 2025 at 18:35):
Thanks!
Filippo A. E. Nuccio (Jun 13 2025 at 18:42):
Follow up question: how did you come up with the numbers & duplicates? Do they show up in your infoview (they don't in mine) or have you used some external tool?
Aaron Liu (Jun 13 2025 at 18:46):
For the number I did a List.length
Aaron Liu (Jun 13 2025 at 18:46):
For the duplicates I just filtered for duplicates
Jovan Gerbscheid (Jun 15 2025 at 16:53):
#25928 defines the gcongr 2 syntax for limiting the depth of gcongr. This is analogous to the congr tactic. It is in addition to the pattern syntax gcongr x + ?_ * _ which also allows you to limit the depth.
Bhavik Mehta (Jun 15 2025 at 17:18):
If you're playing with gcongr, I think it'd be nice to have gcongr? which gives you the pattern it used, analogous to simp?
Jovan Gerbscheid (Jun 15 2025 at 17:20):
Actually, gcongr? already exists. But it is an interactive widget tactic. And it is really poorly implemented. I agree with your suggestion (which means replacing the current one).
Aaron Hill (Jun 16 2025 at 03:21):
Is the inability to rewrite bound variables an intentional part of matching the rw behavior, or could grw eventually get support for it?
Kevin Buzzard (Jun 16 2025 at 06:14):
From the moment we had simp_rw which seemed to be "rw but works" I've always regarded "rw can't rewrite bound variables" as a bug rather than a feature. However rw has never been "fixed" in years. What is the logic here?
Riccardo Brasca (Jun 16 2025 at 07:10):
Wasn't the point that rewriting bound variables requires some sort of functional extensionality?
Riccardo Brasca (Jun 16 2025 at 07:11):
And maybe we don't want a very basic tactic to use additional axioms (this is less of a problem for a mathlib tactic).
Joachim Breitner (Jun 16 2025 at 07:35):
Kevin Buzzard schrieb:
From the moment we had
simp_rwwhich seemed to be "rwbut works" I've always regarded "rwcan't rewrite bound variables" as a bug rather than a feature. Howeverrwhas never been "fixed" in years. What is the logic here?
I believe there is hope that it will be fixed this year :-)
Yaël Dillies (Jun 16 2025 at 07:59):
Riccardo Brasca said:
And maybe we don't want a very basic tactic to use additional axioms (this is less of a problem for a mathlib tactic).
Thought so too, but then split was using choice for the longest time :-)
Ruben Van de Velde (Jun 16 2025 at 08:08):
(Is it not anymore?)
Yaël Dillies (Jun 16 2025 at 08:11):
No! That was fixed alongside another bug
Jovan Gerbscheid (Jun 16 2025 at 10:15):
Aaron Hill said:
Is the inability to rewrite bound variables an intentional part of matching the
rwbehavior, or couldgrweventually get support for it?
I agree it would be better if grw would rewrite bound variables. However, the current version is a lot simpler to implement, which is why I went with this implementation (for now).
Jovan Gerbscheid (Jun 16 2025 at 12:20):
#25942 fixes a bug in the grw tactic: grw currently doesn't work if the goal contains syntheticOpaque metavariables.
Floris van Doorn (Jun 24 2025 at 17:43):
Btw, for easy acccess, the grw PR is #8167.
Etienne Marion (Jun 24 2025 at 17:48):
I just used the tactic for the first time, it's amazing! Thanks a lot for this :tada:
Rémy Degenne (Jun 24 2025 at 17:57):
This looks great! Could it rewrite inside integrals with almost everywhere equalities? Or more generally rewrite with Filter.EventuallyEq
Jovan Gerbscheid (Jun 24 2025 at 18:03):
There are 2 limitations:
- we need to tag more lemmas with
@[gcongr](e.g. forFilter.EventuallyEq) grwcurrently cannot rewrite bound variables. If you do need to rewrite terms containing bound variables, you can use thegcongrtactic directly, which is a bit less convenient, because it requires you to write out the new expression (e.g. in acalcblock).
So feel free to try some examples, and report which ones need tagging.
Floris van Doorn (Jun 24 2025 at 19:09):
Isn't a third limitation that if the relation is =, one or both of {gcongr, grw} currently have a special case to call {congr, rw}, which means that if you want these tactics to use docs#MeasureTheory.integral_congr_ae and then recursively call gcongr on the (now non-=) goal, you're out of luck?
Michael Rothgang (Jul 13 2025 at 15:50):
I was searching for grw in https://leanprover-community.github.io/mathlib-manual/html-multi/Tactics/All-tactics/#all_tactics and didn't find it. @Jon Eugster What's the procedure to make a new tactic appear there?
Jon Eugster (Jul 14 2025 at 05:41):
probably just need to bump the repo again, i.e. update it to the newest mathlib. I'll try to do that later today
Jon Eugster (Jul 14 2025 at 12:50):
Michael Rothgang said:
I was searching for grw in https://leanprover-community.github.io/mathlib-manual/html-multi/Tactics/All-tactics/#all_tactics and didn't find it. Jon Eugster What's the procedure to make a new tactic appear there?
I've bumped the manual to v4.21.0 over lunch, so the tactic is now here:
https://leanprover-community.github.io/mathlib-manual/html-multi//Tactics/All-tactics/#grw
I'll bump it again to v4.22 tonight
Bryan Gin-ge Chen (Jul 14 2025 at 12:53):
If bumping the repo is just a matter of running a script or something, I could try and throw together some automation for it.
Jovan Gerbscheid (Jul 15 2025 at 11:44):
An update on the progress for grw and gcongr:
-
#26907 will hopefully be merged soon, and adds a few features to
gcongr:- Instead of having to tag all of
add_le_add,add_le_add_leftandadd_le_add_right, it will suffice to just tagadd_le_add; any extra reflexive subgoals will be closed usingrfl. This will make future tagging of lemmas easier, (e.g. I'll domul_dvd_mul,neg_dvd_neg,pow_dvd_powandInt.ModEq.of_dvd/Nat.ModEq.of_dvd) @[gcongr]lemmas can have a priority (just like with@[simp]). This will allow to fix #27058gcongroperates in thereducibletransparency instead ofreducible_and_instances. This helps to avoid applying the wrong lemma. This will help to revive #14739.
- Instead of having to tag all of
-
lean#9304 extends the
rfltactic to close goals of the formp → p. This matches the fact thatgcongrtreats→as a binary relation. (unfortunately I can't anymore push directly to the mathlib adaptation branch, so I've made a PR to fix it https://github.com/leanprover-community/mathlib4-nightly-testing/pull/13) - In #27135 I've started to write the
gconverttactic. This is the generalized version of theconverttactic, and this is similarly built upongcongr. For example in
example (h : a * b < c + d) (ha : 0 ≤ a) : a * a < c + c
writing gconvert h will give you the new subgoals a ≤ b and d ≤ c.
Additionally, gconvert will be able to replace the peel tactic.
Future work may include:
- If a
gcongrlemma applies successfully, but it has unsolved side goals, try to find anothergcongrlemma that can satisfy all of its side goals. This would allow to rewrite under multiplication with a negative number (by tagging lemmas likemul_le_mul_of_nonpos_left) - Let
gcongrcall the congruence prodedure ofcongr!on equality and iff goals if nogcongrlemmas apply. - Let
gcongr with x y ztakerintropatterns instead of just variable names (just likecongr!) - Allow
gcongrto go into a lambda e.g. usingPi.le_def.mpr. Maybe we need a@[gext]tag for this. - Allow
gcongrto go into function applications, e.g. usingPi.le_def.mp - Make it easier to tag lemmas like
ENat.coe_le_coe, orFilter.Eventually.monothat aren't quite in the right shape to be agcongrlemma.
Filippo A. E. Nuccio (Jul 15 2025 at 12:10):
One question I've never understood about gcongr is why it fails on "basic" relations (and I suppose that the answer is that "it must", but I am not sure and at any rate I do not know why):
import Mathlib
example (a b c d : ℕ) : a ^ b + c = b ^ c + d := by
congrm ?_ ^ ?_ + ?_ -- works
example (a b c d : ℕ) : a ^ b + c ≤ b ^ c + d := by
gcongr ?_ ^ ?_ + ?_ --works
example (a b c d : ℕ) : a ^ b + c = b ^ c + d := by
gcongr ?_ ^ ?_ + ?_ --fails
Jovan Gerbscheid (Jul 15 2025 at 12:13):
Yes, this is one of the "Future work" points: currently no = or ↔ lemmas have been tagged with gcongr. But instead of going around tagging equality lemmas with gcongr, it would be better to call the congr! tactic internally in the gcongr tactic on = and ↔ goals.
Jovan Gerbscheid (Jul 15 2025 at 12:50):
The motivation for supporting = and ↔ in gcongr (instead of simply calling the congr! tactic) is that there may be cases where we can move from an = or ↔ goal into a more general relation.
Some motivating example are
↔and propositiona ≡ b [ZMOD n], which moves into the[ZMOD n]relation.=and the operation% n, which moves into the[ZMOD n]relation.↔and proposition∀ᶠ x in l, p x/∃ᶠ x in l, p x, which moves into the=ᶠ[l]relation. (Although the lemmaf =ᶠ[l] g → (l.Eventually f ↔ l.Eventually g)seems to be missing in mathlib).=and the operation∫ (a : α), f a ∂μ, which moves into the=ᶠ[ae μ]relation
Jovan Gerbscheid (Jul 26 2025 at 21:37):
Ping for #26907. Could someone review the @[gcongr] tag updates? The meta code has already been approved.
Robert Maxton (Aug 08 2025 at 04:49):
Jovan Gerbscheid said:
The motivation for supporting
=and↔ingcongr(instead of simply calling thecongr!tactic) is that there may be cases where we can move from an=or↔goal into a more general relation.
Another -- somewhat dangerous but occasionally useful -- transformation is going from a difficult = involing casts, to a HEq where the casts can be eliminated and the remaining expression solved using congr!. I am not at all sure how one would build heuristics for when this is a good idea (perhaps 'matches _ = _ ∘ _root_.cast _' or similar might be a good first step, though in e.g. category theory it's less clear how to handle expressions involving eqToHom), but it should probably be on the dock for consideration at least.
Jovan Gerbscheid (Aug 08 2025 at 07:39):
Going from = to HEq sounds like a job for the congr! tactic. I'm not sure to what extent it does this already.
(And when I add the congr! steps to gcongr, then it can also be a job for gcongr)
Jovan Gerbscheid (Aug 08 2025 at 07:41):
One obstacle to getting the congr! steps into gcongr is the fact that gcongr can work with a pattern and congr! doesn't. So the congr! steps first need to be made compatible with using a pattern.
Yuyang Zhao (Aug 16 2025 at 09:56):
Could grw support lemmas like docs#le_of_le_of_antisymmRel ? grw can be used in https://github.com/vihdzp/combinatorial-games/pull/194, but seems it can only be written as grw [(...).le]. In some worse cases, there are only lemmas about operations and AntisymmRel, but no lemmas about operations and \le.
Jovan Gerbscheid (Aug 16 2025 at 10:40):
gcongr usually tries to use the most general relation (so ≤ instead of </AntisymmRel (· ≤ ·)), and then tries to close the goal with possibly a stronger relation. For example we have special support for concluding ≤ from a < hypothesis using le_of_lt. In the same way AntisymmRel can be specially supported.
I've opened #28514 that implements this. In the meantime, you could copy this code into your project, to see if it works.
namespace Mathlib.Tactic.GCongr
variable {α : Type*} {a b : α} {r : α → α → Prop}
lemma AntisymmRel.left (h : AntisymmRel r a b) : r a b := h.1
lemma AntisymmRel.right (h : AntisymmRel r a b) : r b a := h.2
@[gcongr_forward] def exactAntisymmRelLeft : ForwardExt where
eval h goal := do goal.assignIfDefEq (← Lean.Meta.mkAppM ``AntisymmRel.left #[h])
@[gcongr_forward] def exactAntisymmRelRight : ForwardExt where
eval h goal := do goal.assignIfDefEq (← Lean.Meta.mkAppM ``AntisymmRel.right #[h])
end Mathlib.Tactic.GCongr
Jovan Gerbscheid (Aug 16 2025 at 10:43):
If there are lemmas about operations and AntisymmRel, but no lemmas about operations and ≤, then simply tagging the lemmas with @[gcongr] should be sufficient to allow rewriting with AntisymmRel. (Even without #28514)
Yuyang Zhao (Aug 16 2025 at 14:19):
This is very helpful, thank you! But it still doesn't make the last grw work.
section
variable {α} [Preorder α] {a b : α}
infix:50 " ≈ " => AntisymmRel (· ≤ ·)
def f : α → α := sorry
@[gcongr]
lemma f_congr : a ≈ b → f a ≈ f b := sorry
lemma foo (h : a ≈ b) : f a ≈ f b := by
grw [h]
lemma bar (h : a ≈ b) : f a ≤ f b := by
grw [h]
end
Jovan Gerbscheid (Aug 16 2025 at 16:02):
In this case, you can make it work by adding another @[gcongr] lemma:
@[gcongr]
lemma f_congr' : a ≈ b → f a ≤ f b := sorry
I've also been thinking about a heuristic to make this work out of the box, which is to have the grw check if the hypothesis relation is symmetric, and if so, call gcongr with the ↔ relation instead of →. But that might want to wait until ↔ is better supported in gcongr.
Jovan Gerbscheid (Sep 20 2025 at 16:10):
In order to work towards supporting =/↔ in gcongr/grw in the way I described above, we need to have a version of congr! which (in some combination with gcongr) applies congruences until it reaches a certain subexpression. It is already possible to limit the depth (with congr! 3), but that isn't specific enough. Recall that gcongr can also take a pattern (with gcongr 2 * ?_). From what I understand, the new rw tactic that is on the roadmap may come with such a congruence tactic.
Kyle Miller said:
The rough idea @Jovan Gerbscheid is that it will do the rewrite and then do a post-hoc congruence construction
@Kyle Miller, is this right? And do you know approximately when this development will happen?
Heather Macbeth (Sep 20 2025 at 23:53):
Jovan Gerbscheid said:
we need to have a version of
congr!which (in some combination withgcongr) applies congruences until it reaches a certain subexpression. It is already possible to limit the depth (withcongr! 3), but that isn't specific enough. Recall thatgcongrcan also take a pattern (withgcongr 2 * ?_).
This already exists right? The congrm tactic.
Jovan Gerbscheid (Sep 21 2025 at 08:29):
The congrm tactic is a thin wrapper around the congr(...) term elaborator (which is why I had originally discarded this option).
Looking more closely at congr(...), it elaborates the lhs and rhs into a form where the holes are annotated with some metadata, so that congr(...) knows up to where to do congruences. This seems like a reasonable approach to also take in gcongr. This would mean that in the implementation of gcongr, instead of keeping track of the pattern while doing the congruences, we would keep track of these metadata annotations.
Jovan Gerbscheid (Sep 21 2025 at 08:29):
And if want, we could also allow passing proofs directly in the pattern, in the same way that congrm allows:
example (a b : ℝ) (h : a ≤ b) : a + 1 ≤ b + 1 := by
gcongr $h + 1
Jovan Gerbscheid (Oct 24 2025 at 03:05):
I have now reimplemented how patterns work in gcongr, so that they work with metadata, just like in congrm. This is a necessary step for supporting equality reasoning in gcongr, as I discussed above. (#30739)
There is also still the open PR #28339 which will allow tagging lemmas whose conclusion is Monotone _.
Lara Toledano (Dec 11 2025 at 12:53):
The grw tactic allows for
example [TopologicalSpace X] (s t: Set X) (hst : s ⊆ t) (ht : IsMeagre t) : IsMeagre s := by
grw [hst]
exact ht
It would be nice if it could also do the following
example [TopologicalSpace X] (s t: Set X) (hst : s ⊆ t) (hs : ¬IsMeagre s) : ¬IsMeagre t := by
grw [hst]
exact hs
I tried adding a new @[gcongr] lemma
@[gcongr] --error
lemma not_isMeagre_mono [TopologicalSpace X] (s t: Set X) (hst : s ⊆ t) (hs : ¬IsMeagre s) : ¬IsMeagre t := (IsMeagre.mono hst).mt hs
I also tried defining a new predicate.
def NonMeagre [TopologicalSpace X] (s : Set X) := ¬IsMeagre s
@[gcongr]
lemma NonMeagre.mono [TopologicalSpace X] {s t : Set X} (hst : s ⊆ t) (hs : NonMeagre s) :
NonMeagre t := (IsMeagre.mono hst).mt hs
example [TopologicalSpace X] (s t: Set X) (hst : s ⊆ t) (hs : NonMeagre s) : NonMeagre t := by
grw [hst] --error
In this last snippet, I do not understand why the gcongr attribute is added without error, but not used correctly in the example.
Jovan Gerbscheid (Dec 11 2025 at 16:17):
In your second example, you want to rewrite from right to left. So, just like in rw, you should use the left arrow: grw [← hst]
Lara Toledano (Dec 11 2025 at 16:18):
Thank you so much!
example [TopologicalSpace X] (s t: Set X) (hst : s ⊆ t) (hs : ¬IsMeagre s) : ¬IsMeagre t := by
grw [← hst]
exact hs
Last updated: Dec 20 2025 at 21:32 UTC