Zulip Chat Archive
Stream: mathlib4
Topic: Order dual tactic
Violeta Hernández (Dec 08 2024 at 09:34):
I would imagine this has been discussed before but: is there any progress on a tactic alike to_additive that converts a theorem to its order-dual version?
Violeta Hernández (Dec 08 2024 at 09:37):
Something like:
import Mathlib.Order.SuccPred.Limit
open Order
@[to_order_dual] -- IsPredLimit a ↔ a ≠ ⊤ ∧ IsPredPrelimit a
theorem isSuccLimit_iff' [PartialOrder α] [OrderBot α] {a : α} :
IsSuccLimit a ↔ a ≠ ⊥ ∧ IsSuccPrelimit a :=
isSuccLimit_iff
Violeta Hernández (Dec 08 2024 at 09:41):
@Yaël Dillies
Yaël Dillies (Dec 08 2024 at 10:00):
No more than last time, at least as much as I'm aware of. @Calle Sönne was interested in the category theoretical version but has since been plagued with painful wrists like me
Eric Wieser (Dec 08 2024 at 12:19):
One complication here is that you would expect it to sometimes reorder arguments
Yaël Dillies (Dec 08 2024 at 15:04):
And sometimes you want it to generate lemmas for some
Violeta Hernández (Dec 08 2024 at 22:12):
I would imagine that even this basic version could already get rid of a lot of cruft throughout the library
Violeta Hernández (Dec 08 2024 at 22:13):
Other examples of order duals would include Sup and Inf, or IsMin and IsMax, or IsLUB and IsGLB, ...
Bryan Gin-ge Chen (Feb 11 2025 at 13:30):
I've started playing around with using the to_additive infrastructure to make an order_dual tactic in #21719. (#21696 is where I'm putting non-order_dual-specific changes to to_additive.) I'm a relative newcomer to metaprogramming, so I'd be happy to collaborate with anyone who's interested!
I'm currently stuck on a decidable instance which isn't getting translated:
application type mismatch
ite (b ≤ a)
argument has type
Decidable ((fun x1 x2 ↦ x1 ≤ x2) a b)
but function has type
[h : Decidable (b ≤ a)] → α → α → α
Damiano Testa (Feb 11 2025 at 15:00):
I look forward to this very much! In my experiments, the main issue was generating the "correct" ordering of the variable names in the statements, plus some twisted difficulty with associativity for statements involving more than 2 elements.
Bryan Gin-ge Chen (Feb 11 2025 at 15:06):
Ah, sounds like your experiments may have proceeded farther than mine... if you've got any notes or old code still lying around I'd love to take a look.
Damiano Testa (Feb 11 2025 at 15:16):
I think that #12528 was one of the latest experiments that I had. It is not at all polished!
Bryan Gin-ge Chen (Feb 12 2025 at 01:58):
@Damiano Testa I had a quick look at #12528 and it looks like you implemented some useful functions! I'm still working on figuring out the to_additive machinery, but hopefully what you wrote can be plugged in somewhere...
Bryan Gin-ge Chen (Feb 12 2025 at 02:11):
As a general progress report, the very simple approach of just replacing the multiplicative->additive dictionaries with an order-dual dictionary and using to_additive's reorder to reverse the arguments of every ≤ in sight in #21719 is now stuck at LinearOrder.lean and WithBot.lean with different issues. I'll keep digging into what's going on so I can try to ask more focused questions later, but any experts with spare time are welcome to take a look and suggest / push fixes.
Damiano Testa (Feb 12 2025 at 08:49):
If I remember correctly, I did not hook into the to_additive machinery, but made a simple syntactic translation, so I think that your approach probably complements nicely the one that I had started.
Damiano Testa (Feb 12 2025 at 08:50):
I am definitely interested in this automation and I have had it in the back of my mind for a while: I'll help, but also do remind me, in case I get distracted by other projects! :smile:
Bryan Gin-ge Chen (Feb 13 2025 at 03:40):
I made some progress on understanding what is broken in LinearOrder.lean. It turns out the naïve approach of just swapping the arguments to ≤ is incompatible with the definition of max that we're using. Compare:
min := fun a b => if a ≤ b then a else b
max := fun a b => if a ≤ b then b else a
If we just swap the arguments of ≤, then min would get translated to:
max := fun a b => if b ≤ a then b else a
(which is actually what mathlib3 used up until mathlib3#17470).
Now if we change the definition of max back to this, then order_dual seems to work quite well translating theorems about min and max... until we try to define a linear order on the natural numbers, where all hell breaks loose because max is defined in core with the first definition -- which is of course the reason that we had to land mathlib3#17470 in the first place.
It is possible to manually apply the attribute to any lemmas whose proofs unfold max or min to ≤, so I'll try doing that next. I'm hoping we eventually get to a point where the max / min API gets sealed off and order_dual can take over.
Mario Carneiro (Feb 14 2025 at 21:35):
or maybe core just has the wrong definition of max
Mario Carneiro (Feb 14 2025 at 21:36):
and possibly things have changed enough that we can actually have the backport go the other way around
Bryan Gin-ge Chen (Feb 14 2025 at 22:09):
I haven't made it that much further yet but so far the min max messiness seems to be contained to a handful of lemmas.
Mario Carneiro (Feb 14 2025 at 22:10):
I mean sure we can keep working around this but I think the only reason we backported at the time was because core is annoying to PR to and it still kinda is but less so
Mario Carneiro (Feb 14 2025 at 22:11):
I'm almost certain whoever wrote that definition of max did not think about this issue at all and just wrote the first ordering that came to mind
Bryan Gin-ge Chen (Feb 14 2025 at 22:15):
My first reaction was certainly the same: wow things would be much cleaner if we just switched the definition back. But as I tag more and more lemmas with order_dual I'm less and less sure that I'm not just imposing my own arbitrary choices when I tell the tactic to re-order variables.
Bryan Gin-ge Chen (Feb 15 2025 at 04:59):
Today's progress update (all work can be found in #21719): As always, any feedback / questions here or on the PR is welcome!
I've now managed to tag most lemmas where the order_dual applies through the start of Order.Basic. Up to now I've been doing it all "by feel", so I think now is a good time to try and put the process into words, if just for my own benefit. So far most of the declarations order_dual has been applied to are just simple statements about < and ≤, with a few that mention min and max; I did do some tests with WithBot and WithTop as well, but maybe I'll talk about those in a later post.
First, everything starts at the top of Order.Defs.PartialOrder, where I decided that order_dual should transform a ≤ b to b ≤ a (and similarly a < b should be transformed to b < a, etc.):
attribute [order_dual existing (reorder := 3 4) LE.le] LE.le
attribute [order_dual existing (reorder := 3 4) LT.lt] LT.lt
attribute [order_dual existing (reorder := 3 4) GE.ge] GE.ge
attribute [order_dual existing (reorder := 3 4) GT.gt] GT.gt
Here you can see the use of to_additive's reorder option to swap the arguments of these functions.
Now that these have been registered, order_dual can transform theorems or defs that mention any of these functions to their dual versions, where "dual" just means that the LHS and RHS of every ≤, <, etc. in sight have been swapped. Note that a theorem like le_rfl : a ≤ a is manifestly self-dual so there's no reason to tag it with order_dual as the default behavior is to do nothing.
So now suppose we have a declaration where order_dual actually transforms it into something different. Consider the resulting dual declaration:
Case 1: Most of the time, the transformed version is just some slight variation of the original theorem that no one has needed up until now. For the time being, I'm manually naming these theorems as the original name + OD as a suffix:
@[order_dual le_of_lt_or_eqOD] -- {a b : α} (h : b < a ∨ a = b) : b ≤ a
lemma le_of_lt_or_eq (h : a < b ∨ a = b) : a ≤ b := h.elim le_of_lt le_of_eq
@[order_dual le_of_eq_or_ltOD] -- {a b : α} (h : a = b ∨ b < a) : b ≤ a
lemma le_of_eq_or_lt (h : a = b ∨ a < b) : a ≤ b := h.elim le_of_eq le_of_lt
It's a bit unfortunate that this leads to a bunch of new trivial theorems, but we need to add these variants so that order_dual will know how to transform the source theorems properly when they show up in proofs later on.
Case 2: Sometimes we get lucky and the transformed version of a theorem is secretly the same theorem except with some arguments permuted. In that case we just register the theorem as the dual version of itself with reorder:
@[order_dual existing (reorder := 3 4) lt_iff_le_not_le]
-- note that swapping the LHS and RHS of < and ≤ below yields:
-- b < a ↔ b ≤ a ∧ ¬a ≤ b
-- which is also what we get if we swap a and b
lemma lt_iff_le_not_le : a < b ↔ a ≤ b ∧ ¬b ≤ a := Preorder.lt_iff_le_not_le _ _
Case 3: Sometimes we get even more lucky and the transformed theorem happens to be one that is proved later on in the file. This is the happy case where order_dual will let us delete lines of code (though I'm just commenting things out for now):
@[order_dual (attr := trans) gt_of_gt_of_ge]
lemma lt_of_lt_of_le (hab : a < b) (hbc : b ≤ c) : a < c :=
lt_of_le_not_le (le_trans (le_of_lt hab) hbc) fun hca ↦ not_le_of_lt hab (le_trans hbc hca)
@[order_dual (attr := trans) gt_of_ge_of_gt]
lemma lt_of_le_of_lt (hab : a ≤ b) (hbc : b < c) : a < c :=
lt_of_le_not_le (le_trans hab (le_of_lt hbc)) fun hca ↦ not_le_of_lt hbc (le_trans hca hab)
-- @[trans] lemma gt_of_gt_of_ge (h₁ : a > b) (h₂ : b ≥ c) : a > c := lt_of_le_of_lt h₂ h₁
-- @[trans] lemma gt_of_ge_of_gt (h₁ : a ≥ b) (h₂ : b > c) : a > c := lt_of_lt_of_le h₂ h₁
One thing that I've been treating inconsistently is whether I should identify variations that use ge and gt as the same as the ones with le and lt. Also, case is still relatively rare so far, but I expect that the proportion will be higher for pairs of theorems about min max top bot, etc. rather than these theorems about le and lt.
I think we eventually want special order_dual self syntax for order_dual to verify case 2, since it's easy to screw up the reordering. Also, it would be nice to have some automation to search for case 3 theorems as well, since searching for defeq theorems later in the file with a different name than expected is a bit annoying. Previously I was also trying to use reorder to try to match up the output of order_dual with later declarations, but I kept making mistakes, so I ended up undoing all of those and using reorder only when we get back the original declaration. Maybe there's some way to use automation here as well?
I haven't given much thought at all to how order_dual should be changing the names of theorems like these at all - this might help with identifying case 3 declarations. Right now, order_dual only knows to change min to max, bot to top, sup to inf, so theorems about lt and le just end up with the same name.
Violeta Hernández (Feb 15 2025 at 05:35):
(I don't mean to sidetrack you but my sincere thanks for this! It'll really come in handy in a lot of places)
Violeta Hernández (Feb 15 2025 at 05:39):
I think it's fine and even expected that lt and le should be kept the same. The only exception is when this would lead to the entire name being the same for two different theorems, which ime is where a lot of these ge/gt theorems come from.
Violeta Hernández (Feb 15 2025 at 05:41):
Are you planning to hit a certain tagging goal before PRing the tactic? I think it's important to have a working proof of concept before we get this into Mathlib, but tagging everything that could be tagged is complete overkill at this point.
Bryan Gin-ge Chen (Feb 15 2025 at 06:21):
Yeah, I’m not sure how far I should tag before cleaning up the tactic changes. I kind of want to see if I can get to lattices, but maybe it’s already useful enough?
Bryan Gin-ge Chen (Feb 15 2025 at 06:23):
(If anyone feels like pushing some commits tagging more lemmas and defs to my branch while I’m sleeping I certainly don’t mind!)
Mario Carneiro (Feb 15 2025 at 14:26):
Bryan Gin-ge Chen said:
Case 3: Sometimes we get even more lucky and the transformed theorem happens to be one that is proved later on in the file. This is the happy case where
order_dualwill let us delete lines of code (though I'm just commenting things out for now):@[order_dual (attr := trans) gt_of_gt_of_ge] lemma lt_of_lt_of_le (hab : a < b) (hbc : b ≤ c) : a < c := lt_of_le_not_le (le_trans (le_of_lt hab) hbc) fun hca ↦ not_le_of_lt hab (le_trans hbc hca) @[order_dual (attr := trans) gt_of_ge_of_gt] lemma lt_of_le_of_lt (hab : a ≤ b) (hbc : b < c) : a < c := lt_of_le_not_le (le_trans hab (le_of_lt hbc)) fun hca ↦ not_le_of_lt hbc (le_trans hca hab) -- @[trans] lemma gt_of_gt_of_ge (h₁ : a > b) (h₂ : b ≥ c) : a > c := lt_of_le_of_lt h₂ h₁ -- @[trans] lemma gt_of_ge_of_gt (h₁ : a ≥ b) (h₂ : b > c) : a > c := lt_of_lt_of_le h₂ h₁
By the way, I don't exactly consider this example a win, the order_dual tactic produces a proof from scratch by "copy paste" while the original proof was much shorter because it's defeq to an existing lemma
Mario Carneiro (Feb 15 2025 at 14:29):
given the additional issue about gt here I would be enclined to just tag the latter lemma rather than trying to translate it
Bryan Gin-ge Chen (Feb 15 2025 at 14:36):
Yeah, that makes sense! This is along the lines of something I mentioned at the end of my message, but one thing about tagging lemmas after the fact with existing is that order_dual no longer goes through the proof and checks that things translate properly, so it's easy to accidentally add a tag that's nonsense. What I've been doing is temporarily generating a new lemma and then checking with an example that it is indeed defeq to the other declaration - it'd be nice if there was some kind of automated checking here (though I haven't thought through exactly what I want, I guess).
Mario Carneiro (Feb 15 2025 at 15:59):
Bryan Gin-ge Chen said:
but one thing about tagging lemmas after the fact with
existingis thatorder_dualno longer goes through the proof and checks that things translate properly, so it's easy to accidentally add a tag that's nonsense
that sounds fixable? order_dual / to_additive should be able to validate these at attribute application time
Kim Morrison (Feb 16 2025 at 01:48):
Yes, we're open to change the definitions up in Lean, but I'd like to be presented with a "yes, it all works, including Mathlib, and people have signed off on the fact the change really makes tactic implementations easier" PR if possible. I don't have time to do this myself right now.
Bryan Gin-ge Chen (Mar 13 2025 at 20:34):
I finally found some time this week to come back to this and I've added the order_dual self syntax that I mentioned earlier, so tagging self-dual lemmas is now a lot less verbose.
I also attempted to add some validation for order_dual self and order_dual existing to check that the type of the declaration generated by order_dual (after variable reordering) is indeed defeq to the type of the original declaration itself (for self) or the type of the provided declaration (for existing), respectively. However, I'm getting mismatches where there just seems to be a difference in a universe variable:
order_dual failed validation!
tgt type:
∀ {α : Type u_2} [self : Preorder α] (a b c : α), a ≤ b → b ≤ c → a ≤ c,
transformed type:
∀ {α : Type u_1} [inst : Preorder α] (a b c : α), a ≤ b → b ≤ c → a ≤ c
The main validation code consists of these 8 lines or so, and I was already well outside my metaprogramming comfort zone when I wrote them, so there's probably some obvious mistake(s).
Before PRing, I also need to refactor things to separate out the parts of the code which are specific to to_additive, so I would appreciate it anyone has any suggestions on how to do this cleanly; more generally, any comments about the current state of the meta code are very welcome!
Damiano Testa (Mar 13 2025 at 20:46):
I do not know how AttrM works, but couldn't you try to combine the two MetaM.runs into a single one?
Damiano Testa (Mar 13 2025 at 20:47):
I think that the universe level information may be stored in the MetaM state somewhere and maybe the two runs do not keep track of the same assignments?
Bryan Gin-ge Chen (Mar 13 2025 at 21:03):
Thanks Damiano! However, I tried putting isDefEq into the same function that generates the order dual type in the next commit and unfortunately I'm still getting the same errors.
Damiano Testa (Mar 13 2025 at 21:04):
Ok, so maybe "manually" creating fresh (universe) metavariables before unification might help?
Aaron Liu (Mar 13 2025 at 21:04):
How does to_additive do it?
Damiano Testa (Mar 13 2025 at 21:05):
I'm not sure that to_additive has this check.
Damiano Testa (Mar 13 2025 at 21:05):
In fact, today I was goofing around and you can actually place multiple copies of the to_additive attribute on the same declaration and you get new "identical" declarations.
Damiano Testa (Mar 13 2025 at 21:06):
(You need to give them manually different names, though, since the environment catches the name clash otherwise.)
Aaron Liu (Mar 13 2025 at 21:08):
In that case I would instantiate one with universe variables and the other one with fresh universe metavariables, and do a defeq check on those. Then do it the other way around too to be safe.
Bryan Gin-ge Chen (Mar 13 2025 at 21:39):
OK, thanks! I guess the issue is the one that Kyle mentioned in . Unfortunately his snippet starts with Names not Exprs. Is there an easy way to refresh universe metavariables in my use case?
For reference here's the current checking code (far from an MWE, see #21719):
def checkDeclType (b : BundledExtensions)
(tgt : Name) (srcDecl tgtDecl : ConstantInfo) (reorder : List (List Nat) := []) :
MetaM (Expr × Bool) := do
let mut decl := srcDecl.updateName tgt
if 0 ∈ reorder.flatten then
decl := decl.updateLevelParams decl.levelParams.swapFirstTwo
let exp ← applyReplacementFun b <| ← reorderForall reorder
<| ← expand b <| ← unfoldAuxLemmas decl.type
return ⟨exp, ← isDefEq exp tgtDecl.type⟩
and I guess I need to do more setup with exp and tgtDecl.type before passing them to isDefEq.
edit: this was resolved in
Aaron Liu (Mar 13 2025 at 21:55):
You can use docs#Lean.ConstantInfo.instantiateTypeLevelParams
Damiano Testa (Mar 13 2025 at 21:57):
If I understand correctly, Bryan wants to do this for the "new" expr, the one that does not have a corresponding declaration.
Aaron Liu (Mar 13 2025 at 22:06):
You can docs#Lean.ConstantInfo.updateType an existing declaration.
Jovan Gerbscheid (Mar 14 2025 at 01:06):
You mentioned having trouble with the order on WithBotand WithTop. Note that there is some ongoing work on refactoring these definitions at #19668, by @Yaël Dillies.
Bryan Gin-ge Chen (Mar 14 2025 at 03:29):
For the time being I've just turned the validation error into a logInfo so I can continue. Interestingly, I'm now seeing panics in later files coming from the reorderForall function where sometimes the validation function is sending an expression with too few arguments. So there's probably something else wrong in my approach here...
Jovan Gerbscheid (Mar 14 2025 at 12:45):
In the reorderForall function, you accidentally replaced mkForallFVars by mkLambdaFVars, which gives the mismatch.
Bryan Gin-ge Chen (Mar 14 2025 at 13:19):
Ah that was silly of me! Fixing that does get rid of most of the mismatches, but it looks like there are still two remaining coming from:
/-
reorderForall tried to reorder a list that was too small:
forall {α : Type.{u_2}} [self : LinearOrder.{u_2} α],
DecidableRel.{succ u_2, succ u_2} α α (fun (x1._@.Mathlib.Order.Defs.LinearOrder._hyg.636 : α)
(x2._@.Mathlib.Order.Defs.LinearOrder._hyg.636 : α) =>
LE.le.{u_2} α (Preorder.toLE.{u_2} α (PartialOrder.toPreorder.{u_2} α (LinearOrder.toPartialOrder.{u_2} α self))) x1._@.Mathlib.Order.Defs.LinearOrder._hyg.636 x2._@.Mathlib.Order.Defs.LinearOrder._hyg.636)
#[_uniq.1658, _uniq.1659]
[[2, 3]]
-/
attribute [order_dual self (reorder := 3 4)] LinearOrder.decidableLE
/-
reorderForall tried to reorder a list that was too small:
forall {α : Type.{u_2}} [self : LinearOrder.{u_2} α],
DecidableRel.{succ u_2, succ u_2} α α (fun (x1._@.Mathlib.Order.Defs.LinearOrder._hyg.661 : α)
(x2._@.Mathlib.Order.Defs.LinearOrder._hyg.661 : α) =>
LT.lt.{u_2} α (Preorder.toLT.{u_2} α (PartialOrder.toPreorder.{u_2} α (LinearOrder.toPartialOrder.{u_2} α self))) x1._@.Mathlib.Order.Defs.LinearOrder._hyg.661 x2._@.Mathlib.Order.Defs.LinearOrder._hyg.661)
#[_uniq.1691, _uniq.1692]
[[2, 3]]
-/
attribute [order_dual self (reorder := 3 4)] LinearOrder.decidableLT
in Order.Defs.LinearOrder.
If I had to guess, probably the functions from to_additive don't handle DecidableRel well?
Jovan Gerbscheid (Mar 14 2025 at 13:40):
A priori DecidableRel is not a forall. Only after unfolding it, does it become a forall (Luckily it is reducible, so in the reducible transparency it is a forall). So the fix is probably to replace some ForallTelescope by ForallTelescopeReducing. (And making sure you are in the reducible tranparency)
Jovan Gerbscheid (Mar 14 2025 at 13:52):
Although I'm not sure if reducing abbreviations is wanted in general. To avoid unnecessarily unfolding reducibles, you can use ForallBoundedTelescopeReducing (or something like that). This one only unfolds up to how many foralls you want to have.
Jovan Gerbscheid (Mar 14 2025 at 16:13):
I don't think I fully understand what the role/purpose is of reordering the arguments in LinearOrder.decidableLE. Can you explain why they should be swapped?
Bryan Gin-ge Chen (Mar 14 2025 at 16:33):
I can't say I fully understand it either, but without the reordering it seems like the instance doesn't apply when we need it to. Looks like the first failure is (edit: let me look for the actual first failure...)lt_trichotomy:
Jovan Gerbscheid (Mar 14 2025 at 16:38):
I looked at docs#Decidable.lt_or_eq_of_le, and I'm confused why this theorem exists, because it is weaker than docs#lt_or_eq_of_le. I thought mathlib was a classical mathematics library.
Damiano Testa (Mar 14 2025 at 16:39):
I think that it is a classical mathematical library outside the Decidable namespace.
Ruben Van de Velde (Mar 14 2025 at 16:41):
This theorem dates back to the core lean 3 repository. I'm not sure anyone would mind if it disappeared
Bryan Gin-ge Chen (Mar 14 2025 at 16:49):
OK, if I remove Decidable from the proof of lt_trichotomy, then order_dual (without reordering in LinearOrder.decidableLE) is happy with it, however things blow up just a little bit further below:
/-
application type mismatch
dite (a ≤ b)
argument has type
Decidable ((fun x1 x2 ↦ x1 ≤ x2) b a)
but function has type
∀ [h : Decidable (a ≤ b)],
(a ≤ b → b < a ∨ a ≤ b) → (¬a ≤ b → b < a ∨ a ≤ b) → b < a ∨ a ≤ b
-/
@[order_dual lt_or_leOD]
lemma lt_or_le (a b : α) : a < b ∨ b ≤ a :=
if hba : b ≤ a then Or.inr hba else Or.inl <| lt_of_not_ge hba
Aaron Liu (Mar 14 2025 at 16:55):
Can you show this with pp.explicit? The problem is in the invisible instance.
Bryan Gin-ge Chen (Mar 14 2025 at 16:56):
Sure, here you go!
application type mismatch
@dite
(Or (@LT.lt α (@Preorder.toLT α (@PartialOrder.toPreorder α (@LinearOrder.toPartialOrder α inst✝))) b a)
(@LE.le α (@Preorder.toLE α (@PartialOrder.toPreorder α (@LinearOrder.toPartialOrder α inst✝))) a b))
(@LE.le α (@Preorder.toLE α (@PartialOrder.toPreorder α (@LinearOrder.toPartialOrder α inst✝))) a b)
(@LinearOrder.decidableLE α inst✝ b a)
argument has type
Decidable
((fun x1 x2 ↦ @LE.le α (@Preorder.toLE α (@PartialOrder.toPreorder α (@LinearOrder.toPartialOrder α inst✝))) x1 x2)
b a)
but function has type
∀
[h :
Decidable (@LE.le α (@Preorder.toLE α (@PartialOrder.toPreorder α (@LinearOrder.toPartialOrder α inst✝))) a b)],
(@LE.le α (@Preorder.toLE α (@PartialOrder.toPreorder α (@LinearOrder.toPartialOrder α inst✝))) a b →
Or (@LT.lt α (@Preorder.toLT α (@PartialOrder.toPreorder α (@LinearOrder.toPartialOrder α inst✝))) b a)
(@LE.le α (@Preorder.toLE α (@PartialOrder.toPreorder α (@LinearOrder.toPartialOrder α inst✝))) a b)) →
(Not (@LE.le α (@Preorder.toLE α (@PartialOrder.toPreorder α (@LinearOrder.toPartialOrder α inst✝))) a b) →
Or (@LT.lt α (@Preorder.toLT α (@PartialOrder.toPreorder α (@LinearOrder.toPartialOrder α inst✝))) b a)
(@LE.le α (@Preorder.toLE α (@PartialOrder.toPreorder α (@LinearOrder.toPartialOrder α inst✝))) a b)) →
Or (@LT.lt α (@Preorder.toLT α (@PartialOrder.toPreorder α (@LinearOrder.toPartialOrder α inst✝))) b a)
(@LE.le α (@Preorder.toLE α (@PartialOrder.toPreorder α (@LinearOrder.toPartialOrder α inst✝))) a b)
Yaël Dillies (Mar 14 2025 at 16:58):
It's the classic "(if a ≤ b then a else b) = (if b ≤ a then b else a) is not defeq", no?
Aaron Liu (Mar 14 2025 at 17:03):
I think the easiest way to fix this would just be to reorder the arguments to LinearOrder.decidableLE.
Bryan Gin-ge Chen (Mar 14 2025 at 17:04):
Right, the proof was working when attribute [order_dual self (reorder := 3 4)] LinearOrder.decidableLE was not commented out, so (as in the short version of the error) I think it's just that the known decidability of ≤ doesn't apply to (fun x2 x1 ↦ x1 ≤ x2).
Aaron Liu (Mar 14 2025 at 17:17):
It looks like you need to change these lines to a docs#Lean.Meta.forallBoundedTelescope.
Bryan Gin-ge Chen (Mar 14 2025 at 17:26):
Thanks! Looks like that worked. Now it's just the issue with the universe names confusing isDefEq.
Jovan Gerbscheid (Mar 14 2025 at 18:58):
In your implementation, you didn't pass a bound to forallBoundedTelescope, which kind of defeats the purpose of using forallBoundedTelescope over forallTelescopeReducing. You can instead pass it the bound reorder.flatten.max? (and if the Option value is none, then don't telescope at all, since there is nothing to reorder)
Bryan Gin-ge Chen (Mar 14 2025 at 19:21):
Makes sense! I guess I'll do the same thing with lambdaBoundedTelescope in reorderLambda.
Jovan Gerbscheid (Mar 14 2025 at 19:29):
Oh and I said earlier to use the reducible transparency, but I take that back, because now that we're using a bounded telescope, it will only reduce if explicitly told to do so. (Assuming you didn't modify the transparency yourself, it's set to default by default, so you don't need to change it)
Bryan Gin-ge Chen (Mar 14 2025 at 19:35):
Right, I didn't change the transparency at all.
Here's what I have now (I had to add a +1 to the bound but it works):
/-- Reorder pi-binders. See doc of `reorderAttr` for the interpretation of the argument -/
def reorderForall (reorder : List (List Nat) := []) (src : Expr) : MetaM Expr := do
if let some maxReorder := reorder.flatten.max? then
forallBoundedTelescope src (some (maxReorder + 1)) fun xs e => do
if xs.size > maxReorder then
mkForallFVars (xs.permute! reorder) e
else
dbg_trace "reorderForall tried to reorder a list that was too small:\n\
{src}\n{xs}\n{reorder}"
return src
else
return src
/-- Reorder lambda-binders. See doc of `reorderAttr` for the interpretation of the argument -/
def reorderLambda (reorder : List (List Nat) := []) (src : Expr) : MetaM Expr := do
if let some maxReorder := reorder.flatten.max? then
lambdaBoundedTelescope src (maxReorder + 1) fun xs e => do
if xs.size > maxReorder then
mkLambdaFVars (xs.permute! reorder) e
else
dbg_trace "reorderLambda tried to reorder a list that was too small:\n\
{src}\n{xs}\n{reorder}"
return src
else
return src
Jovan Gerbscheid (Mar 14 2025 at 19:40):
I would write "if xs.size = maxReorder + 1" (although this shouldn't change the effect of the code)
Bryan Gin-ge Chen (Mar 14 2025 at 20:00):
Is there a reason to prefer that over xs.size > maxReorder?
Jovan Gerbscheid (Mar 14 2025 at 20:02):
I think it makes it more readable, and it would throw an error if we accidentally passed the wrong bound to the bounded telescope
Jovan Gerbscheid (Apr 21 2025 at 13:52):
@Bryan Gin-ge Chen, I've had a look at the decidableLE issue, and one solution I've found is to define decidableGE, which is the dual of decidableLE, as follows:
abbrev DecidableGT (α : Type*) [LT α] := DecidableRel (fun a b : α ↦ LT.lt b a)
abbrev DecidableGE (α : Type*) [LE α] := DecidableRel (fun a b : α ↦ LE.le b a)
attribute [order_dual existing DecidableGT] DecidableLT
attribute [order_dual existing DecidableGE] DecidableLE
However, I think a cleaner solution would be to refactor DecidableLE to not be an abbrev, but instead a structure. Then we can tag its projection function with order_dual
Bryan Gin-ge Chen (Apr 21 2025 at 13:59):
Thanks for taking a look! Right, I thought briefly about having a dual class, but it does indeed seem messy. DecidableLE and friends are defined in core, right? I wonder if they had some specific reasons for making it an abbrev rather than a structure.
I also wonder if there's a way to use metaprogramming to have order_dual reorder the arguments to instances appropriately when the type contains ≤ or something else tagged with the "reorder" attribute.
Jovan Gerbscheid (Apr 21 2025 at 14:06):
Bryan Gin-ge Chen said:
I also wonder if there's a way to use metaprogramming to have
order_dualreorder the arguments to instances appropriately when the type contains≤or something else tagged with the "reorder" attribute.
I presume you're talking about for example lt_or_eq_of_le, where order_dual gives the error
application type mismatch
@dite (b < a ∨ a = b) (a ≤ b) (inst✝ b a)
argument
inst✝ b a
has type
Decidable (b ≤ a) : Type
but is expected to have type
Decidable (a ≤ b) : Type
Although it is true that swapping a and b here solves the problem, I think this is hard to acomplish automatically. The same problem would be solved by either making decidableLE a structure, or giving it a dual decidableGE.
Jovan Gerbscheid (Apr 21 2025 at 14:11):
I think core only defined DecidableLE quite recently. Before that it was just DecidableRel LE.le (which would work better with order_dual). Anyways I think there is no real downside to defining DecidableLE as a structure.
Bryan Gin-ge Chen (Apr 21 2025 at 14:14):
Right, I see how making DecidableLE a structure or giving it a dual will solve the issue, but I'd like to better understand why it'd be hard to deal with instances automatically.
It's certainly out of reach of my current metaprogramming abilities, but doesn't something like the following algorithm work? Whenever the proof that we're dualizing contains an instance term, first dualize its type and note all reordered arguments, then use that to swap the arguments to the instance?
Bryan Gin-ge Chen (Apr 21 2025 at 14:15):
In any case, let's ping @Kim Morrison to see whether making DecidableLE a structure in core would be acceptable.
Jovan Gerbscheid (Apr 21 2025 at 14:21):
In the part where you say "first dualize its type", what exactly would decidableLE α be dualized to? I think in your model it wouldn't be dualized. So then "note all reordered arguments" would have to look at something else than the usual reorder
Bryan Gin-ge Chen (Apr 21 2025 at 14:25):
Ah, OK I see now. I was imagining that we could see through the abbrev, but obviously that doesn't happen for free...
Aaron Liu (Apr 21 2025 at 14:36):
Why doesn't reorder just work?
Jovan Gerbscheid (Apr 21 2025 at 14:45):
What @Bryan Gin-ge Chen wanted to do is reorder arguments a and b in inst b a. But inst is a local variable, so it cannot be globally tagged with reorder.
Aaron Liu (Apr 21 2025 at 14:52):
Just reorder on DecidableLE then, it's a @[reducible, inline] def defined in Prelude
Aaron Liu (Apr 21 2025 at 14:52):
or does that not work for some reason
Aaron Liu (Apr 21 2025 at 14:55):
Do it the same way you reorder LE.le
Jovan Gerbscheid (Apr 21 2025 at 14:57):
Which arguments of DecidableLE do you want to swap? It only takes 2 arguments: a type and an instance.
Aaron Liu (Apr 21 2025 at 14:57):
The third and fourth argument :)
Aaron Liu (Apr 21 2025 at 14:57):
wait, that doesn't work
Aaron Liu (Apr 21 2025 at 14:58):
hmmmmmm
Aaron Liu (Apr 21 2025 at 14:59):
I guess the best way to do this is to reorder inst
Aaron Liu (Apr 21 2025 at 14:59):
Since it's a local variable, it must have been introduced into the context at some point
Aaron Liu (Apr 21 2025 at 15:00):
Then you can just mark it as reorder when it's introduced
Aaron Liu (Apr 21 2025 at 15:00):
and that's based on the type
Aaron Liu (Apr 21 2025 at 15:02):
I guess this is the price you pay for having dependent types
Aaron Liu (Apr 21 2025 at 15:03):
Maybe this could be a new attribute? Something like type_reorder
Jovan Gerbscheid (Apr 21 2025 at 15:28):
I think this may cause some problems if you have both decidableLE and decidableRel LE.le. Because one of them gets dualized by swapping the arguments to the local instance, and the other by dualizing the type of the local instance. So we end up with two reducibly defEq expressions that get dualized to two non-defEq expressions. This could give rise to very obscure failures
Aaron Liu (Apr 21 2025 at 15:35):
oh, I can see how that could cause problems
Bryan Gin-ge Chen (Apr 21 2025 at 18:25):
Is there a library design constraint lurking here? Something more general than "don't use abbrevs to define classes if you want to be able to use order_dual".
Jovan Gerbscheid (Apr 21 2025 at 18:29):
What sort of constraint do you mean? In my experience, using abbrevs to define classes can lead to trouble, but it is convenient because it saves you from writing many instances.
I had a go at implementing DecidableLE as a structure, but to be able to use that, I think we'd need to rewrite all type class instances such as e.g. instance (a b : Int16) : Decidable (a ≤ b) into the form instance : DecidableLE Int16, which is kind of annoying to do for all of Lean and Mathlib.
On the other hand, using the abbrev DecidableGE isn't actually that bad, because type class search can automatically infer DecidableLE and DecidableGE from eachother.
Bryan Gin-ge Chen (Apr 21 2025 at 20:05):
OK, I was a bit hesitant to add another class, but if DecidableGE doesn't end up being too hard to work with then we can go with that. One minor downside is that theorems that use DecidableLE can't be self-dual anymore, but I don't think there are that many theorems which use DecidableLE anyways.
Aaron Liu (Apr 21 2025 at 20:07):
Can typeclass infer DecidableGE from DecidableLE?
Jovan Gerbscheid (Apr 21 2025 at 20:10):
Yes, because they unfold to ∀ a b, Decidable a ≤ b, and ∀ a b, Decidable b ≤ a respectively. And Lean introduces the ∀ variables as metavariables.
Jovan Gerbscheid (Apr 21 2025 at 22:39):
I made some progress using the approach with DecidableGE and pushed my changes. Some comments:
- When I write
@[order_dual self (attr := simp)], does it apply thesimptag once or twice to the same lemma? I tried instead writing@[simp, order_dual self], but then the linter complains that it wants 'both' lemmas tagged withsimp - I generally prefer to not change statements of lemmas in the process of using
order_dual. For example I noticed you setge_transto be the dual ofle_trans, but this means that it is now defined in terms of≤instead of≥. Instead, by reordering the arguments in the right way,le_transandge_transcan both be self-dual. -
I know there was the idea to change the definition of
maxorminin core to make it a bit more convenient fororder_dual. But I think there is actually a good reason for the specific definitions in core, which is written in a comment atmaxOfLeandminOfLe:
-- Marked inline so that `min x y + max x y` can be optimized to a single branch.
So I think the point is thatminandmaxhave the same condition so that using both of them together generates efficient code. -
To make tagging easier, would it be sensible to have special support for
aliases? If A is an alias of B, then the dual of A should just be the dual of B. - To make tagging easier, can there be a syntax (or lack of syntax) to indicate that the dual name should be the name followed by
OD, if the name would otherwise be the same?
Bryan Gin-ge Chen (Apr 21 2025 at 23:12):
When I write
@[order_dual self (attr := simp)], does it apply thesimptag once or twice to the same lemma?
Good question. applyAttributes and copyMetadata don't check if src and tgt are the same; is applying attributes not idempotent?
The other bullet points / suggestions sound good to me other than the last one. The OD suffix was basically me being lazy and putting off thinking about names, so I'm hesitant to make it a permanent default; I'm fine if we make it a temporary syntax / convention though (i.e. add a TODO in the comments).
Aaron Liu (Apr 21 2025 at 23:16):
I would suggest making better names, but if you're just testing and the names are temporary then I guess it's fine.
Jovan Gerbscheid (Apr 22 2025 at 06:40):
I think whether attribute application is idempotent depends on the implementation of that attribute, (i.e. does it use a HashSet or an Array)
Jovan Gerbscheid (Apr 22 2025 at 06:44):
For the names, the main case in which I didn't know how to do the naming is if the theorem contains both ≤ and =, like le_of_eq. Because the order dual swaps the arguments in ≤ but not in =, so it's almost the same theorem again. Do we pick a keyword to append to the name? Like le_of_eq_swapped?
Jovan Gerbscheid (Apr 22 2025 at 06:58):
Some more points:
- Attribute syntax: I noticed that the syntax of
order_dualtakes the arguments self, existing, (reorder := ..) and (attr := ..), in any order (and even multiple times). I think it would be nicer for consistency to pick one order and enforce that. I've been going fororder_dual (self/existing)? attr? reorder? name?. - Syntax hovering: it would be nice if the attribute syntax had better hover information. This should include an example of the attribute syntax and the required order of arguments (from the above point), and also the hover information for the
attr :=attributes. And more generally a basic explanation oforder_dual. simpIff lemmas: for iff lemmas tagged withsimp, lean uses some weird auxiliary lemma instead of just applying prop_ext every time. I ran into this issue yesterday and I could solve it by squeezing thesimp, but that isn't a nice solution.
Yaël Dillies (Apr 22 2025 at 07:39):
Jovan Gerbscheid said:
For the names, the main case in which I didn't know how to do the naming is if the theorem contains both ≤ and =, like
le_of_eq. Because the order dual swaps the arguments in ≤ but not in =, so it's almost the same theorem again. Do we pick a keyword to append to the name? Likele_of_eq_swapped?
Naming convention is ge_of_eq
Jovan Gerbscheid (Apr 22 2025 at 07:41):
Do you mean that out of these three
le_of_eq : a = b → a ≤ b
le_of_eq' : a = b → b ≤ a
ge_of_eq : a = b → a ≥ b
the middle one should be called ge_of_eq?
Jovan Gerbscheid (Apr 22 2025 at 08:33):
le_trans' : b ≤ c → a ≤ b → a ≤ c
ge_trans : a ≥ b → b ≥ c → a ≥ c
These two lemmas both exist, but your convention seems to imply le_trans' should be called ge_trans?
Edward van de Meent (Apr 22 2025 at 08:34):
actually, in my opinion that one should be le_comp
Edward van de Meent (Apr 22 2025 at 08:35):
since it's precisely the difference between comp and trans, applied to le
Jovan Gerbscheid (Apr 22 2025 at 08:36):
So would you suggest removing the current ge_trans in favour of the current le_trans'?
Edward van de Meent (Apr 22 2025 at 08:37):
i don't see why yet
Jovan Gerbscheid (Apr 22 2025 at 08:43):
I find it a bit confusing with the ≤ vs ≥ notation and le vs ge naming. For the notation, appart from a few exceptions, we always use ≤. And I thought that this should be reflected in the name: always use le for ≤. But @Yaël Dillies is suggesting to use ge in the name for ≤ if the lemma is generated by the order dual tactic. Don't you think that this would be very confusing?
Edward van de Meent (Apr 22 2025 at 08:48):
yes. i guess the issue is that in our typical naming scheme, we hardly track where which free variables end up, which is typically precisely the difference between le and ge
Yaël Dillies (Apr 22 2025 at 08:50):
Jovan Gerbscheid said:
@Yaël Dillies is suggesting to use
gein the name for≤if the lemma is generated by the order dual tactic. Don't you think that this would be very confusing?
No, this is not the naming convention. The naming convention is approximately
- Turn
foo_le_barintobar_le_foo - If that fails (because eg
foo=baror there is another ambiguity), instead replacelewithge
Jovan Gerbscheid (Apr 22 2025 at 08:51):
So far I'm mostly looking at lemmas where the two sides of le are free variables, so point 1 doesn't apply.
Yaël Dillies (Apr 22 2025 at 08:52):
Then le → ge it is. But I must warn you that point 1 really is the one that applies most often in practice
Yaël Dillies (Apr 22 2025 at 08:52):
(I know it makes the naming algorithm much harder, there's a reason why I failed to write to_dual for several years!)
Edward van de Meent (Apr 22 2025 at 08:53):
i guess it might be best if we can reserve an option to explicitly name the dual lemma?
Jovan Gerbscheid (Apr 22 2025 at 08:54):
Oh right, should we call it to_dual instead of order_dual? It is really about any kind of dual.
Jovan Gerbscheid (Apr 22 2025 at 08:54):
Yes, we can choose to override the the automatically generated name for the dual lemma
Yaël Dillies (Apr 22 2025 at 08:55):
Yes, eg @Calle Sönne is hoping to steal all your work on this tactic and apply it to category theory :smile:
Jovan Gerbscheid (Apr 22 2025 at 11:58):
Ok, I'll make a PR to clean up some of these lemmas to use the gt and ge naming, but using < and ≤. But first I have #24277 to remove some @[trans] annotations.
Bryan Gin-ge Chen (Apr 22 2025 at 13:05):
If we want to_dual to dualize both order-theoretically and category-theoretically at the same time, then it might already be possible to start tagging theorems now with the current state of the attribute.
Jovan Gerbscheid (Apr 22 2025 at 13:37):
Some of the changes to the to_additive infrastructure should probably be PR-ed before to_dual itself. Your check that to_additive existing does the correct thing is very useful, and will probably find some mistakes in mathlib. (In fact I found one by accident here #24283).
Additionally I think reorderForall and reorderLambda should throw an error if the permutation numbers are out of bounds.
Jovan Gerbscheid (Apr 25 2025 at 21:59):
@Yaël Dillies, I'm trying to replace lemmas about > with lemmas about <, but still calling them gt when appropriate. Do you think the following lemmas should use ge/gt in the name?
lt_or_le (a b : α) : a < b ∨ b ≤ a
lt_or_lt_of_ne (h : a ≠ b) : a < b ∨ b < a
the naming conventions seem to suggest the names I wrote, but the names lt_or_ge and lt_or_gt_of_ne also seem natural.
Yaël Dillies (Apr 25 2025 at 22:02):
Both pairs of names are fine (and possibly we should have both)
Yaël Dillies (Apr 25 2025 at 22:02):
Jovan Gerbscheid (Apr 26 2025 at 01:56):
Do you also think Ne.lt_of_le' : b ≠ a → a ≤ b → a < b should be renamed to Ne.gt_of_ge?
Yaël Dillies (Apr 26 2025 at 06:50):
This one is a bit tougher in the sense that I think of it as having the != reversed, not the <= and <
Jovan Gerbscheid (Apr 26 2025 at 12:12):
I have tagged a bunch of category theory results now with order_dual. I ran into a few issues:
- Applying attributes in the case of
order_dual self. I write@[simp, order_dual self (reorder := ..)], but the linter complains that it wants@[order_dual self (attr := simp) (reorder := ..)]. So if we want the first form, then we should change the linter to complain about the second option instead of the first. - Higher order functions. A few functions require swapping arguments inside an argument in order to be
order_dualto themself, such asPrefunctor.mk {V : Type u₁} [Quiver V] {W : Type u₂} [Quiver W] (obj : V → W) (map : {X Y : V} → (X ⟶ Y) → (obj X ⟶ obj Y)) : V ⥤q W(need to swapXandY). I could imagine extending thereordersyntax to support this, but that seems overly complicated, and otherwise we can just have such constants not be self-dual. IsThinsuffers the same problem asdecidableLE, so we get the dualIsThin'which is equivalent toIsThin.- When tagging a structure projection function with
order_dual (reorder := ..), the reordering doesn't apply if the projection is in the form of anExpr.proj. So I think we need to refold all of theExpr.projexpressions into their projection functions before transforming the expression. This comes up because thereassocattribute creates such proof terms.
Jovan Gerbscheid (Apr 26 2025 at 12:18):
I noticed there is a special meaning when the number 0 appears in the reorder := .. syntax, which swaps the order of the first two universe variables. I think this is very confusing, and it isn't used or documented anywhere. Should we remove it?
Jovan Gerbscheid (Apr 26 2025 at 12:19):
Have you considered using == instead of isDefEq to check that the generated and actual order dual expressions are the same? I would expect that they should satisfy ==.
Bryan Gin-ge Chen (Apr 26 2025 at 12:52):
Jovan Gerbscheid said:
I noticed there is a special meaning when the number
0appears in thereorder := ..syntax, which swaps the order of the first two universe variables. I think this is very confusing, and it isn't used or documented anywhere. Should we remove it?
I didn't realize this was unused. Maybe @Floris van Doorn has an opinion on this? (It seems the feature was added along with the reorder syntax in !3#7888)
Bryan Gin-ge Chen (Apr 26 2025 at 12:53):
Jovan Gerbscheid said:
Have you considered using
==instead ofisDefEqto check that the generated and actual order dual expressions are the same? I would expect that they should satisfy==.
I didn't consider it at all -- do you think it'd be beneficial? I can test it out when I have a chance, but if you're currently hacking on order_dual, maybe you're better poised to do so at the moment. I'd be interested to know the results.
Jovan Gerbscheid (Apr 26 2025 at 12:55):
I also renamed it to to_dual just now :grinning:
Jovan Gerbscheid (Apr 26 2025 at 13:11):
Ah actually I got confused about the off-by-one property of reorder. So what happens is that if reorder := .. contains a 1, then it reorders the first two universe variables, which is required for HPow -> HSMul.
Jovan Gerbscheid (Apr 26 2025 at 13:12):
But I do think this could lead to surprising behaviour, as it is a hack.
Jovan Gerbscheid (Apr 26 2025 at 15:43):
I tried using ==, which works fine. The change made it so that LinearOrder.toDecidableLE can't be self dual, but that is fine, as we can define LinearOrder.toDecidableGE. I also discovered the following weird universe thing in the process:
run_meta do -- (α : Type u) → [inst : LT α] → Type (max 0 u)
let info ← Lean.getConstInfo ``DecidableLT
Lean.logInfo m! "{info.type}"
Anyways, we do need a way to turn off the verification with syntax like self! or existing! (which I saw you suggested in a comment). The place where verification breaks is the constructor CategoryTheory.Iso.mk, where we need to swap the fields hom_inv_id : hom ≫ inv = 𝟙 X := by aesop_cat and inv_hom_id : inv ≫ hom = 𝟙 Y := by aesop_cat, but the two by aesop_cat autoParams aren't == to eachother (which is a bit unfortunate)
Jovan Gerbscheid (Apr 27 2025 at 11:47):
I made a PR (#24401) that introduces to_dual's verification step into to_additive, and I had to fix around 20 files where warnings were shown.
Jovan Gerbscheid (Apr 27 2025 at 11:50):
Also, my PR refactoring order lemmas is now at the new #24396, because in the previous PR it was getting unmanageable to fix all of the broken proofs. So instead I've added the old names as aliases, with a TODO to deprecate them.
Floris van Doorn (Apr 27 2025 at 23:32):
Bryan Gin-ge Chen said:
Jovan Gerbscheid said:
I noticed there is a special meaning when the number
0appears in thereorder := ..syntax, which swaps the order of the first two universe variables. I think this is very confusing, and it isn't used or documented anywhere. Should we remove it?I didn't realize this was unused. Maybe Floris van Doorn has an opinion on this? (It seems the feature was added along with the reorder syntax in !3#7888)
Oh, I thought I had documented that.
It is definitely used in to_additive. The universes have to be reordered correctly as well, and as a hack, I only implemented the following case: if the first two arguments are reordered, to_additive assumes that both of them are types, and then also reorders the first two universe levels. This is a very common scenario, and was necessary for many lemmas to_additive generates with the reorder option. Not reordering them would cause failures when declarations using the faulty one are additivized.
Of course, this is a hack. Feel free to implement a more robust solution.
Floris van Doorn (Apr 27 2025 at 23:35):
Oh, I now read further and see that Jovan also realized this.
Jovan Gerbscheid (Apr 28 2025 at 09:34):
(Note that the universes are swapped when doing any permutation involving the first argument, not just a swap with the second argument)
Jovan Gerbscheid (Apr 28 2025 at 16:31):
Should we use export LinearOrder (le_total min_def max_def) instead of having two identical lemmas in the root and LinearOrder namespaces? The duplication is a bit annoying for tagging.
Yaël Dillies (Apr 28 2025 at 18:21):
Jovan Gerbscheid said:
Should we use
export LinearOrder (le_total min_def max_def)instead of having two identical lemmas in the root andLinearOrdernamespaces? The duplication is a bit annoying for tagging.
It's useful to not use export because then the lemmas show up under the expected name in the docs (hopefully doc-gen can be fixed to avoid needing this hack)
Jovan Gerbscheid (Jul 23 2025 at 11:28):
I have fixed the merge conflicts in the to_dual PR (#21719), and I've made a new PR to to_additive to define the to_additive self syntax (#27387).
Here at the Class Field Theory formalization event, some people would like to have the to_dual attribute for homology and cohomology.
Bryan Gin-ge Chen (Jul 23 2025 at 12:08):
The way I structured the to_dual PR was to have it depend on #21696 which starts to split up the to_additive code into bits that can be re-used by to_dual. Should we make an effort to revive that one too or should we just try and merge #21719 all in one go? I'll see if I can make some time to work on to_dual / review related PRs this week; just ping me or DM me if you have some tasks I can help with.
Jovan Gerbscheid (Jul 23 2025 at 12:35):
I think we can merge it all in one go, but I don't really mind.
Jovan Gerbscheid (Jul 23 2025 at 12:36):
I think in an ideal world, it would be possible to declare a new translation attribute with a one-liner. And then we would do this for to_additive and for to_dual. This would require an extra argument isDual : Bool which determines whether to add a reverse translation for every given translation.
Jovan Gerbscheid (Jul 23 2025 at 12:43):
Jovan Gerbscheid said:
I think we can merge it all in one go, but I don't really mind.
Although I am trying to PR independent features one-by-one whenever possible, to reduce the size of the final PR.
Jovan Gerbscheid (Jul 23 2025 at 22:21):
Thanks for the quick review! Here is another PR that ports a fix from the to_dual PR: #27405
Another thought: to_dual and reassoc have a funny interaction. For lemmas currently tagged @[reassoc] that we want to tag with to_dual, we want the lemma and its dual to have a reassoc version. But those are not dual to eachother, so we will need to add their duals as well. So we end up with 6 lemmas. So I guess we should define some @[to_dual_reassoc] for this, which adds both the _assoc and _assocRev lemmas.
Jovan Gerbscheid (Jul 29 2025 at 00:21):
In #21719, I have now split out the to_dual code into a separate file, so that ToAdditive.Frontend is changed as little as possible. It is now mostly ready to be PR-ed :tada:. I think the PR will have to contain only a little bit of tagging things with @[to_dual], and we can leave the rest for future PRs.
One more TODO is to fix the error messages. It currently will tell you that "to_additive failed" even though you actually used to_dual. I'm less worried about trace messages and comments in the code mentioning to_additive.
Another TODO is to figure out how to deal with associativity of composition in category theory, and the reassoc attribute. But that question doesn't need to block to_dual from being used for order theory.
Bryan Gin-ge Chen (Jul 29 2025 at 01:38):
Thanks so much for your work on this Jovan! I'll try and take a look at this in the next few days and maybe knock off some of the TODOs, unless you're still planning on making more big changes.
Violeta Hernández (Jul 29 2025 at 06:26):
I wasn't aware that this was still being worked on! What's the progress status?
Jovan Gerbscheid (Jul 29 2025 at 07:34):
The progress status is that essentially all the prerequisites are now in place: to_additive existing now has a check in place to verify that the attribute can be applied. We now have the to_additive self syntax as a special case of existing. The new naming converntion for le/ge has been applied.
In #21719, I've also been tagging a bunch of lemmas about orders, and category theory. This can be ported in later PRs, but there will still be a lot more tagging to do.
Jovan Gerbscheid (Aug 03 2025 at 21:00):
I made a new PR for the to_dual attribute in #27887, and it is now ready for review! It only adds @[to_dual] tags in files that directly need to import ToDual, namely Order/Defs/PartialOrder, Order/Notation and Combinatorics/Quiver/Basic.
When this gets merged, we will have to figure out how to best review PRs that expand the use of to_dual.
- Reviewing tags of the form
to_dual self (reorder := ...)orto_dual existing (reorder := ...)is easy, because the attribute checks automatically that thereorderargument is correct. - But for
to_dual (reorder := ...), it is much trickier to check that the generated lemma matches the original lemma. And I don't know if we even want it to become exactly the same; do we care about the order of implicit arguments?
Bryan Gin-ge Chen (Aug 03 2025 at 23:16):
If when we tag, we always start by trying to_dual existing (reorder := ...) original_lemma, and then only delete original_lemma afterwards, then we can take advantage of the checking, right? I guess this doesn't make it easy for reviewers to check unless we require separate commits.
Jovan Gerbscheid (Nov 16 2025 at 23:03):
The to_dual PR has been merged!
Now, I'd like people's opinion on #31722, where I change the (reorder := ...) notation to use 0-indexed counting instead of 1-indexed counting. Personally, I would have expected this to use 0-indexed counting. But if people prefer counting that starts at 1, let me know.
Bryan Gin-ge Chen (Nov 16 2025 at 23:04):
/poll How should the (reorder := ...) notation be indexed?
Bryan Gin-ge Chen (Nov 16 2025 at 23:15):
Are there any other tactics that have syntax for refering to arguments by position?
The only one that comes to my mind isn't a real example: nth_rewrite is apparently 1-indexed, but that's for the nth occurrence of some expression in the rewrite target, not for counting arguments.
Jovan Gerbscheid (Nov 16 2025 at 23:29):
There is the @[specialize] compiler attribute, which allows you to specify arguments by name or by index, and the number is 1-indexed.
Jovan Gerbscheid (Nov 16 2025 at 23:32):
There is also the conv mode syntax that uses numbers to specify which argument to zoom into. But in that case, 0 refers to the head function, and then the arguments are counted from 1.
Kevin Buzzard (Nov 17 2025 at 06:16):
If you want more opinions then you might want to give an example or two of current behaviour, I have no idea what this question is about but I could imagine having an opinion if i saw some examples
Jovan Gerbscheid (Nov 17 2025 at 08:04):
Here are some examples, using the current 1-indexed counting.
swapping `a ^ n` into `n • a`.
attribute [to_additive existing (reorder := 1 2, 5 6) hSMul] HPow.hPow
@[to_additive (reorder := 5 6) smul_apply] -- swapping f and a
lemma pow_apply [∀ i, Pow (M i) α] (f : ∀ i, M i) (a : α) (i : ι) : (f ^ a) i = f i ^ a := ...
-- turning `a < b` into `b < a` etc.
attribute [to_dual self (reorder := 3 4)] LE.le LT.lt GE.ge GT.gt
@[to_dual self (reorder := 3 4)] -- swapping a and b
lemma lt_or_ge {α : Type*} [LinearOrder α] (a b : α) : a < b ∨ b ≤ a := ...
@[to_dual self (reorder := 3 4, 5 6, 9 10)]
lemma homOfEq_eq_iff {V : Type*} [Quiver V] {X Y X' Y' : V}
(f : X ⟶ Y) (g : X' ⟶ Y') (hX : X = X') (hY : Y = Y') :
Quiver.homOfEq f hX hY = g ↔ f = Quiver.homOfEq g hX.symm hY.symm := ...
Jovan Gerbscheid (Nov 17 2025 at 08:06):
I wonder whether it would be helpful to also allow referring to the arguments by name. This would probably make it easier, but there could also be a cost to having multiple ways of saying the same thing.
Kevin Buzzard (Nov 17 2025 at 08:23):
You are counting things, so shouldn't this start at one? For example in homOfEq_eq_iff I'm sure you would accept that you're talking about the 3rd, 4th, 5th, 6th, 9th and 10th inputs to the function
Robin Arnez (Nov 17 2025 at 11:35):
It would probably be simplest if you could also refer to the names of parameters instead of counting them, similar to how @[specialize] does it, so you would do:
attribute [to_additive existing (reorder := α β, 5 6) hSMul] HPow.hPow
@[to_additive (reorder := f a) smul_apply]
lemma pow_apply [∀ i, Pow (M i) α] (f : ∀ i, M i) (a : α) (i : ι) : (f ^ a) i = f i ^ a := ...
-- turning `a < b` into `b < a` etc.
attribute [to_dual self (reorder := 3 4)] LE.le LT.lt GE.ge GT.gt
@[to_dual self (reorder := a b)]
lemma lt_or_ge {α : Type*} [LinearOrder α] (a b : α) : a < b ∨ b ≤ a := ...
@[to_dual self (reorder := X Y, X' Y', hX hY)]
lemma homOfEq_eq_iff {V : Type*} [Quiver V] {X Y X' Y' : V}
(f : X ⟶ Y) (g : X' ⟶ Y') (hX : X = X') (hY : Y = Y') :
Quiver.homOfEq f hX hY = g ↔ f = Quiver.homOfEq g hX.symm hY.symm := ...
Floris van Doorn (Nov 17 2025 at 11:47):
Note that tactics like nth_rw and pick_goal are also 1-indexed.
I think when you're doing metaprogramming, things should be 0-indexed, but for "user-facing" tactics/commands/attributes it should be 1-indexed.
Yaël Dillies (Nov 17 2025 at 14:03):
I'm personally always surprised to see that nth_rw starts counting at 1. It is unintuitive to me.
Damiano Testa (Nov 17 2025 at 14:05):
Whatever the numbering is, would it be possible to add a flag somewhere that, if set, logs a message at the specified location, instead of performing whatever computation the command is supposed to do?
Jovan Gerbscheid (Nov 17 2025 at 14:09):
What sort of flag do you mean?
Damiano Testa (Nov 17 2025 at 14:10):
Something like to_additive --dry-run ....
Damiano Testa (Nov 17 2025 at 14:11):
Or maybe the output of to_additive? already does this, or can be extended to make this more explicit?
Jireh Loreaux (Nov 17 2025 at 14:12):
At this point, I don't really care about this sort of thing, I just don't want it to change! The confusing bit for me with nth_rw wasn't that it starts counting at 1, it's that it used to start counting at 0, and then I had to relearn. So, I don't really care, as long as the behavior (whether 1-indexed or 0-indexed) is documented in the docstring.
Jireh Loreaux (Nov 17 2025 at 14:13):
Ideally, the referring to the parameters by name would be best.
Jovan Gerbscheid (Nov 17 2025 at 14:18):
@Damiano Testa If you mean that you want to have a log of what the attribute does, then you can use to_additive?.
Damiano Testa (Nov 17 2025 at 14:19):
What I was hoping for would be something where by writing
@[to_additive (reorder := 5 6) smul_apply] -- swapping f and a
lemma pow_apply [∀ i, Pow (M i) α] (f : ∀ i, M i) (a : α) (i : ι) : (f ^ a) i = f i ^ a := ...
what is going to be reordered is underlined, and if I change 5 or 6, then the new positions are underlined. So, some "live" signaling of what is going on.
Jovan Gerbscheid (Nov 17 2025 at 14:20):
Jireh Loreaux said:
Ideally, the referring to the parameters by name would be best.
I agree. I will implement this, and then we can use parameter names, rather than indices, most of the time. And then I don't really mind anymore which indexing style is used.
Jovan Gerbscheid (Nov 17 2025 at 14:23):
@Damiano Testa I see, though that sounds quite fancy. What you can do, is see the type of smul_apply when you hover over it, so you can see what was swapped.
Damiano Testa (Nov 17 2025 at 14:29):
Ok, I often would like to see a more extensive use of the Expr.diff functionality that is used in the infoview.
Jovan Gerbscheid (Nov 17 2025 at 17:59):
Here's my PR: #31748
As a bonus, I've added hover info to the arguments, so you can see the argument's name and type.
Jovan Gerbscheid (Nov 19 2025 at 17:49):
I have tagged the content of Order.Defs.LinearOrder in #31709. One issue I found is that there is a duplication of lemmas such as LinearOrder.min_def and min_def. I think we should treat LinearOrder.min_def as an implementation detail, and only use min_def, so LinearOrder.min_def should not be tagged with to_dual. I raised this issue in #mathlib4 > Should duplicated class projections be private? , and I made the PR #31808 to remove all uses of these internal lemmas.
Yaël Dillies (Nov 19 2025 at 17:51):
Yes, they were never meant to be used
Jovan Gerbscheid (Nov 21 2025 at 16:14):
In #31902, I implement a heuristic for guessing the (reorder := ...) argument when using to_dual self or to_dual existing. This means that in the example above, we can simply write
@[to_dual self]
lemma homOfEq_eq_iff {V : Type*} [Quiver V] {X Y X' Y' : V}
(f : X ⟶ Y) (g : X' ⟶ Y') (hX : X = X') (hY : Y = Y') :
Quiver.homOfEq f hX hY = g ↔ f = Quiver.homOfEq g hX.symm hY.symm := ...
Violeta Hernández (Nov 28 2025 at 11:34):
Has this tactic been "released" yet? i.e. can I and should I start tagging things?
Jovan Gerbscheid (Nov 28 2025 at 12:21):
Yes, sort of, we are now in the process of tagging things in mathlib. The problem is that this has to be done in a top-down fashion, so we first need to tag all of the most basic files, in order, in order to be able to use it in more complicated files. I currently have a few open PRs doing just that.
Violeta Hernández (Nov 28 2025 at 14:16):
@Jovan Gerbscheid I'd like to help by adding to_dual to docs#Order.IsSuccPrelimit and docs#Order.IsSuccLimit. What do I need to edit in order for theorem names to be translated properly?
Jovan Gerbscheid (Nov 28 2025 at 19:47):
In the current set-up, Limit gets translated into Colimit. I don't know if we should remove this translation from the dictionary. But even if we don't, we can still get this to work. You'll have to add an entry to the fixAbbreviations dictionary, so that it turns SuccColimit into SuccPrelimit.
And for the other direction, you can simply add the translation from Prelimit to Limit directly to the main guessName disctionary.
Violeta Hernández (Nov 28 2025 at 19:49):
Oh, I should have specified. The dual of IsSuccPrelimit is IsPredPrelimit. And the dual of IsSuccLimit is IsPredLimit.
Violeta Hernández (Nov 28 2025 at 19:49):
So presumably that's what I want to add to fixAbbreviations?
Jovan Gerbscheid (Nov 28 2025 at 19:58):
Aha, we can add the translations between succ and pred to the main dictionary, and remove limit from it. Or we can keep the limit to colimit translation and work around it with fixAbbreviations.
Violeta Hernández (Nov 28 2025 at 19:59):
We definitely want succ ↔ pred in either case, since docs#Order.succ should also get dualized to docs#Order.pred
Violeta Hernández (Nov 28 2025 at 20:00):
Perhaps we can remove limit ↔ colimit for the moment? And judge whether to add it back if someone makes use of it.
Jovan Gerbscheid (Nov 29 2025 at 23:20):
When we get a bit further with the tagging, we will get to SemilatticeSup, which is dual to SemilatticeInf, and for example these two lemmas are dual:
theorem sSup_le_sSup {s t : Set α} (h : s ⊆ t) : sSup s ≤ sSup t
theorem sInf_le_sInf {s t : Set α} (h : s ⊆ t) : sInf t ≤ sInf s
The interesting thing to note here is that even though s ⊆ t is an alternative spelling to s ≤ t, we don't actually swap the s and t. There is also a long-term plan to have ⊆ and ≤ be different notations for the same underlying constant (similar to how max a b and a ⊔ b are different notations for the same constant).
So, we should tag Set with @[to_dual_dont_translate], so that we never translate order structures on Set.
Jovan Gerbscheid (Dec 04 2025 at 19:22):
Today, I had an idea for dealing with definitions like DecidableLT for which we want that the dual is not the same as the dual of the value. The idea is to pre-process (proof) terms in such a way thay they do not rely on unfolding DecidableLT. This can be done by inserting some rewrite or some cast in the right places in the expression.
I made the draft PR #32438, which shows some basic cases working for DecidableLT, WCovBy and Set.Icc.
Violeta Hernández (Dec 04 2025 at 22:35):
image.png
Am I doing something wrong? This gives me an error "expected PredOrder to have a value"
Jovan Gerbscheid (Dec 04 2025 at 22:37):
It is not yet supported to generate structures automatically, so you will have to do the following:
import Mathlib.Order.ConditionallyCompleteLattice.Basic
import Mathlib.Order.Cover
import Mathlib.Order.Iterate
variable {α β : Type*}
/-- Order equipped with a sensible successor function. -/
class SuccOrder (α : Type*) [Preorder α] where
/-- Successor function -/
succ : α → α
/-- Proof of basic ordering with respect to `succ` -/
le_succ : ∀ a, a ≤ succ a
/-- Proof of interaction between `succ` and maximal element -/
max_of_succ_le {a} : succ a ≤ a → IsMax a
/-- Proof that `succ a` is the least element greater than `a` -/
succ_le_of_lt {a b} : a < b → succ a ≤ b
/-- Order equipped with a sensible predecessor function. -/
@[to_dual (attr := ext) SuccOrder]
class PredOrder (α : Type*) [Preorder α] where
/-- Predecessor function -/
pred : α → α
/-- Proof of basic ordering with respect to `pred` -/
pred_le : ∀ a, pred a ≤ a
/-- Proof of interaction between `pred` and minimal element -/
min_of_le_pred {a} : a ≤ pred a → IsMin a
/-- Proof that `pred b` is the greatest element less than `b` -/
le_pred_of_lt {a b} : a < b → a ≤ pred b
Jovan Gerbscheid (Dec 04 2025 at 22:38):
(I am providing it the name SuccOrder manually because the succ ↔ pred translation is not yet in the guessName dictionary)
Violeta Hernández (Dec 04 2025 at 22:38):
I'll add it when I make my PR :slight_smile:
Jovan Gerbscheid (Dec 04 2025 at 22:42):
Eventually I'd like better support for tagging structures and generating the reorders there automatically, but for the time being you will also need to add
attribute [to_dual (reorder := a b)] PredOrder.le_pred_of_lt
To update the reorder argument to be correct for PredOrder.le_pred_of_lt/SuccOrder.succ_le_of_lt
Violeta Hernández (Dec 04 2025 at 22:45):
Hmm... I'm running across a new error
image.png
Violeta Hernández (Dec 04 2025 at 22:45):
Is to_dual set up to work with OrderDual?
Jovan Gerbscheid (Dec 04 2025 at 22:49):
Unfortunately, to_dual cannot yet reorder arguments of arguments. This means that it cannot translate PredOrder.mk to SuccOrder.mk correctly. This should not come up too often, but you've encountered one of these cases now. While we don't have this feature, we will need to declare both instances separately, and link them with to_dual existing.
Violeta Hernández (Dec 04 2025 at 23:03):
More problems :frown:
image.png
Violeta Hernández (Dec 04 2025 at 23:04):
I think the issue is the same, with arguments of arguments, but the validation doesn't even let me add the to_dual existing attribute
Jovan Gerbscheid (Dec 04 2025 at 23:06):
Exactly. The attribute knows that it cannot reorder arguments of arguments, so it disallows adding the to_dual existing attribute here, because this would lead to wrong translations later on anyways.
Aaron Liu (Dec 04 2025 at 23:13):
somehow this doesn't feel like a very smart attribute
Violeta Hernández (Dec 04 2025 at 23:14):
It's quite a useful attribute as is, and I trust that it'll get better :slight_smile:
Violeta Hernández (Dec 04 2025 at 23:24):
How does to_dual interface with alias? If I have something like
alias ⟨foo, bar⟩ := baz
and I want to override both names, do I just have to split it up into?
@[to_dual foo']
alias ⟨foo, _⟩ := baz
@[to_dual bar']
alias ⟨_, bar⟩ := baz
Jovan Gerbscheid (Dec 04 2025 at 23:27):
Yeah, in general I think
@[attrs]
alias ⟨foo, bar⟩ := baz
is equivalent to
alias ⟨foo, bar⟩ := baz
attribute [attrs] foo bar
So you could also make it
alias ⟨foo, bar⟩ := baz
attribute [to_dual foo'] foo
attribute [to_dual bar'] bar
Violeta Hernández (Dec 04 2025 at 23:30):
Sorry I'm troubleshooting so much but I just came across another issue :frown:
image.png
Violeta Hernández (Dec 04 2025 at 23:30):
Is this perhaps fixed by #32438?
Jovan Gerbscheid (Dec 04 2025 at 23:35):
Yes exactly. WCovBy has the property that the unfolded value of a ⩿ b is not dual to the unfolded value of b ⩿ a. So currently, to_dual doesn't work here, and #32438 should fix this.
Violeta Hernández (Dec 04 2025 at 23:53):
oh damn I just noticed Monotone and co. haven't been tagged
Violeta Hernández (Dec 04 2025 at 23:53):
I should probably start there
Jovan Gerbscheid (Dec 04 2025 at 23:53):
Monotone is problematic for the same reason WCovByis :(
Violeta Hernández (Dec 04 2025 at 23:54):
oh :frown:
Violeta Hernández (Dec 05 2025 at 00:07):
I take it that the order dual tactic can't yet handle pi types?
import Mathlib.Order.Iterate
variable [Preorder α] {f : α → α}
@[to_dual iterate_le_id_of_le_id']
theorem id_le_iterate_of_id_le' (h : id ≤ f) (n : ℕ) : id ≤ f^[n] := by
simpa only [Function.iterate_id] using monotone_id.iterate_le_of_le h n
Violeta Hernández (Dec 05 2025 at 00:07):
It's not flipping h
Jovan Gerbscheid (Dec 05 2025 at 00:11):
It does work, we are just missing a tag :)
import Mathlib.Order.Iterate
variable [Preorder α] {f : α → α}
attribute [to_dual existing le_iterate_of_le] Monotone.iterate_le_of_le
@[to_dual iterate_le_id_of_le_id']
theorem id_le_iterate_of_id_le' (h : id ≤ f) (n : ℕ) : id ≤ f^[n] := by
simpa only [Function.iterate_id] using monotone_id.iterate_le_of_le h n
Violeta Hernández (Dec 05 2025 at 00:11):
Oh, thanks!
Violeta Hernández (Dec 05 2025 at 00:58):
I notice OrderTop and OrderBot haven't been dualized
Violeta Hernández (Dec 05 2025 at 00:58):
Any obstacle, or just no one has done it yet?
Jovan Gerbscheid (Dec 05 2025 at 00:59):
See #32377
Violeta Hernández (Dec 05 2025 at 00:59):
Oh, awesome! I'll review it :slight_smile:
Violeta Hernández (Dec 05 2025 at 01:43):
Just opened #32447 for SuccOrder and PredOrder
Violeta Hernández (Dec 05 2025 at 07:36):
Also opened #32459 for the Directed file
Violeta Hernández (Dec 05 2025 at 07:39):
...I'm realizing an issue
Violeta Hernández (Dec 05 2025 at 07:40):
IsDirected α (· ≤ ·) gets dualized to IsDirected α fun x y ↦ y ≤ x which is def-eq but not syntactically equal to IsDirected α (· ≥ ·) and apparently that messes up typeclass lookup
Violeta Hernández (Dec 05 2025 at 07:41):
Perhaps the solution is to make abbrevs IsDirectedLE α and IsDirectedGE α, use those instead, and mark them as dual?
Violeta Hernández (Dec 05 2025 at 08:07):
Why not #32462
Jovan Gerbscheid (Dec 05 2025 at 08:27):
Are you sure it messes up type class lookup? I don't think it should be a problem because the things are defEq in the reducible transparency.
Violeta Hernández (Dec 05 2025 at 08:50):
It does seem to mess up some things. Here's an example:
import Mathlib.Order.Bounds.Basic
variable {α : Type*} [Preorder α]
attribute [-instance] OrderDual.isDirected_ge OrderDual.isDirected_le
@[to_dual isDirected_le]
instance isDirected_ge [IsDirected α (· ≤ ·)] : IsDirected αᵒᵈ (· ≥ ·) := by
assumption
-- The theorem below fails if you delete this
instance isDirected_le' [IsDirected α (· ≥ ·)] : IsDirected αᵒᵈ (· ≤ ·) :=
isDirected_le
theorem union [IsDirected α (· ≥ ·)] {s t : Set α} :
BddBelow s → BddBelow t → BddBelow (s ∪ t) :=
@BddAbove.union αᵒᵈ _ _ _ _
Violeta Hernández (Dec 05 2025 at 09:07):
I think the abbrevs are probably a good thing anyways? Stops people from trying to write things like IsDirected α fun x y ↦ y ≤ x or IsDirected α (swap (· ≤ ·))
Jovan Gerbscheid (Dec 05 2025 at 10:20):
Oh wow, thats interesting. The discrimination tree normalization procedure repeatedly does eta-reduction, and unfolding the head constant. So fun x y ↦ x ≥ y get reduced to GE.ge which gets reduced to fun x y ↦ y ≤ x.
But, fun x y ↦ y ≥ x does not get reduced to fun x y ↦ x ≤ y!
Violeta Hernández (Dec 05 2025 at 19:07):
@Jovan Gerbscheid Ehm, I believe you've somehow introduced a bug in #32377
Violeta Hernández (Dec 05 2025 at 19:12):
It's making #32477 fail all of a sudden. I'll attempt to minimize it. I think it has to do with typeclass search going through some weird path LinearOrder → LT which doesn't get properly dualized.
Violeta Hernández (Dec 05 2025 at 19:19):
Adding the lines
@[to_dual bar]
theorem foo {α : Type*} [Lattice α] {a b : α} : a < b → a < b := id
right below docs#Lattice on latest Mathlib makes the to_dual tactic fail with the error
@[to_dual] failed. The translated value is not type correct. For help, see the docstring of `to_additive`, section `Troubleshooting`. Failed to add declaration
bar:
Application type mismatch: The argument
inst✝.toSemilatticeInf
has type
SemilatticeInf α
but is expected to have type
SemilatticeSup α
in the application
@SemilatticeSup.toPartialOrder α inst✝.toSemilatticeInf
Violeta Hernández (Dec 05 2025 at 19:23):
Oh, this seems to be fixed by adding
attribute [to_dual existing] Lattice.toSemilatticeInf
Jovan Gerbscheid (Dec 05 2025 at 19:29):
Violeta Hernández said:
Jovan Gerbscheid Ehm, I believe you've somehow introduced a bug in #32377
Does that fix this problem?
Violeta Hernández (Dec 05 2025 at 19:29):
I'm currently recompiling but I think it does
Jovan Gerbscheid (Dec 05 2025 at 19:35):
I see. Indeed the instance LinearOrder -> LE goes through SemilatticeInf (which seems a bit silly), so now you indeed need to tag Lattice.toSemilatticeInf
Violeta Hernández (Dec 05 2025 at 22:16):
Just opened #32488 dualizing WithTop and WithBot
Violeta Hernández (Dec 07 2025 at 00:47):
Might I ask what the status on #32438 is?
Violeta Hernández (Dec 08 2025 at 01:56):
Do you think there's a way to get to_dual to work on docs#Lex and docs#Colex ?
Violeta Hernández (Dec 08 2025 at 01:58):
The idea is that on pi types α → β and the like, Colex (α → β) means Lex (αᵒᵈ → β), so it just dualizes one argument
Aaron Liu (Dec 08 2025 at 01:59):
so there are two ways to dualize?
Aaron Liu (Dec 08 2025 at 02:00):
since I think (Lex (α → β))ᵒᵈ is the same as Lex (α → βᵒᵈ)
Violeta Hernández (Dec 08 2025 at 02:10):
Aaron Liu said:
since I think
(Lex (α → β))ᵒᵈis the same asLex (α → βᵒᵈ)
That's true but I don't believe that's a def-eq
Violeta Hernández (Dec 08 2025 at 02:10):
Yep, there's two ways to dualize
Jovan Gerbscheid (Dec 08 2025 at 07:20):
Violeta Hernández said:
Might I ask what the status on #32438 is?
I'm on holiday now, so won't work on it for this week. I wrote in the PR description what is still TODO, but other than those things, it is ready for review.
Violeta Hernández (Dec 08 2025 at 08:28):
Happy holidays! I'll tag whatever else I can in the meanwhile.
Kim Morrison (Dec 09 2025 at 01:06):
Sorry, what is the deal with #32488? I think it is essential that we drop the defeq between WithTop/WithBot and Option --- it leaks through all over the place and makes working with WithTop/WithBot very unpleasant. I have been working on this at #27918, and #32488 has introduced fundamental merge conflicts.
Does someone have a plan for proceeding? I would really appreciate if someone (else) could just get this finished.
Violeta Hernández (Dec 09 2025 at 01:08):
I don't believe #32488 really changes the situation? All the to_dual tactic does is autogenerate the same definitions and lemmas that already existed.
Violeta Hernández (Dec 09 2025 at 01:08):
I do agree that we want to get rid of the def-eq. I can draft #32498 in the meanwhile, since that will also introduce new merge conflicts.
Violeta Hernández (Dec 09 2025 at 01:10):
Another option might be to first tag everything here with to_dual and then perform the def-eq dropping. That way we only need to do half the work.
Violeta Hernández (Dec 09 2025 at 01:11):
I'm willing to take this over
Kim Morrison (Dec 09 2025 at 01:13):
That's great!
If you could resolve the merge conflicts on #27918 that would be a great start. Thanks.
Violeta Hernández (Dec 09 2025 at 01:13):
Of course! Can I push to your branch?
Kim Morrison (Dec 09 2025 at 01:14):
(Context: the FRO interval arithmetic plans are not moving as fast as we'd like, but still hope to eventually get there, and I would really like WithTop/WithBot to be usable by then, as they will be part of the user extension API.)
Kim Morrison (Dec 09 2025 at 01:14):
Let me check if I need to give your permissions.
Kim Morrison (Dec 09 2025 at 01:15):
You should have an invite now.
Violeta Hernández (Dec 09 2025 at 01:28):
Hm, I see an issue. to_dual doesn't seem to like the fact that WithBot is an inductive while WithTop is a (definitionally equal) def.
Violeta Hernández (Dec 09 2025 at 01:28):
Do we want them to be definitionally equal with each other?
Violeta Hernández (Dec 09 2025 at 01:28):
Perhaps a solution is to make some auxiliary structure, and declare WithBot and WithTop both def-eq to it
Violeta Hernández (Dec 09 2025 at 01:29):
Though surely the long term solution is to not have them be def-eq and use to_dual for our order dualizing purposes.
Violeta Hernández (Dec 09 2025 at 01:37):
Just pushed a merge commit to the branch. I've used this "auxiliary structure" solution for now, but it's a dirty hack and in the future we surely want to just dualize the definition for WithBot itself.
Kim Morrison (Dec 09 2025 at 01:41):
Violeta Hernández said:
Do we want them to be definitionally equal with each other?
I think in the long run, no, we don't. However the decision in previous discussions around #27918 was that this was too difficult to break on the first pass.
Violeta Hernández (Dec 09 2025 at 01:43):
This also applies to docs#WithBot.instLE and docs#WithTop.instLE, right? I found that design a bit odd when I added the to_dual tags on #32499.
Violeta Hernández (Dec 09 2025 at 02:03):
Did you delete some files? The build fails immediately
Violeta Hernández (Dec 09 2025 at 02:42):
@Kim Morrison The grind cases attribute doesn't seem to work on WithBot anymore. Any fix or way to circumvent this?
Jovan Gerbscheid (Dec 10 2025 at 08:41):
Violeta Hernández said:
Hm, I see an issue.
to_dualdoesn't seem to like the fact thatWithBotis an inductive whileWithTopis a (definitionally equal) def.
I think there should be no fundamental obstacle to this. It's just that nobody has tried/tested it before.
Jovan Gerbscheid (Dec 10 2025 at 08:50):
But I think the easiest approach is to first tag everything with to_dual, and then changing the definition of WithBot should be easy.
Kim Morrison (Dec 10 2025 at 22:04):
Violeta Hernández said:
Kim Morrison The
grind casesattribute doesn't seem to work onWithBotanymore. Any fix or way to circumvent this?
No, this is why I made it an inductive.
Kim Morrison (Dec 10 2025 at 22:05):
but I guess this can be a feature request. Any chance you could provide me with a #mwe of what we want from grind cases?
Jovan Gerbscheid (Dec 18 2025 at 13:07):
I've made #33036 for order homs/embeddings/equivs. This one was a bit tricky because the definition for embeddings/equivs unfolds to something that is not self-dual. This is the problem that #32438 addresses. Unfortuately, #32438 doesn't fix this instance of the problem.
Luckily, we don't tend to rely on unfolding this definition in practice, so we can work around this.
Violeta Hernández (Dec 18 2025 at 17:13):
Could you help me out with #32459? I don't understand why this randomly turned some instance on ENNReal noncomputable.
Jovan Gerbscheid (Dec 18 2025 at 17:33):
I don't see anything about ENNReal or noncomputable there? I do see a type class synthesis failure for IsDirectedOrder that may or may not go away if you merge master.
Violeta Hernández (Dec 18 2025 at 17:33):
Ah! Wrong PR, sorry. One sec
Violeta Hernández (Dec 18 2025 at 17:33):
I meant #32626
Violeta Hernández (Dec 19 2025 at 00:03):
Incidentally, I've merged master on #32459. It should hopefully be ready for review. (If it doesn't build, I'll fix it tomorrow, I'm getting really sleepy)
Jovan Gerbscheid (Dec 20 2025 at 10:49):
The PR for unfold boundaries (#32438) should now be ready for review. It introduces two new commands: to_dual_insert_cast and to_dual_insert_cast_fun (I'm still not quite sure what the commands should really be called), and they are used as follows:
to_dual_insert_cast Monotone := by grind
to_dual_insert_cast_fun DecidableLE := fun inst a b ↦ inst b a, fun inst a b ↦ inst b a
to_dual_insert_cast requires you to prove an equality between the dual and the dual of its value.
to_dual_insert_cast_fun requires you to give a dual to the unfold and refold functions. This for cases where proving equality doesn't make sense, in particular DecidableLE and DecidableLT, because they are types.
Last updated: Dec 20 2025 at 21:32 UTC