Zulip Chat Archive
Stream: mathlib4
Topic: Bringing back ⇑
Eric Wieser (Nov 09 2023 at 13:28):
In Lean4, core only supports ↑. We added a custom elaborator in mathlib that lets us write ⇑ and ↥ (though it's buggy until #8297). Kyle Miller added support for delaboration of ↥ for pretty much all the cases we care about.
Do we want ⇑ to appear in the goal view instead of ↑ for functions? Should this be a mathlib-only feature or in Std?
Eric Wieser (Nov 09 2023 at 13:29):
/poll What do you prefer to see in your goal view
- ↑f (x + y) = ↑f x + ↑f y (the status quo)
- ⇑f (x + y) = ⇑f x + ⇑f y
Eric Wieser (Nov 09 2023 at 13:30):
Arguments in favor of ⇑:
- It should round-trip better
- It allows use of the heuristic "
⇑s are always boring,↑s could be hiding something unexpected"
Eric Wieser (Nov 09 2023 at 13:31):
Arguments against:
- It's one more magic arrow to teach new users
Alex J. Best (Nov 09 2023 at 13:52):
Do we have actual examples of situations where round tripping is better? Those would be helpful to know when deciding
Eric Wieser (Nov 09 2023 at 13:59):
Sure, here's an example:
import Mathlib.Algebra.Group.Hom.Basic
import Mathlib.Tactic.Coe
example {M N : Type*} [Monoid M] [Monoid N] (f : M →* N) (x y : M) : f x = f y := by
show (⇑f) x = (⇑f) y -- works
show ↑f x = ↑f y -- fails
sorry
Eric Wieser (Nov 09 2023 at 13:59):
(the extra parens will go away after #8297)
Patrick Massot (Nov 09 2023 at 14:15):
In Lean 3, you needed to remove all ⇑ as a very first step when trying to get round-tripping.
Patrick Massot (Nov 09 2023 at 14:16):
And #8297 doesn't build.
Eric Wieser (Nov 09 2023 at 14:17):
Yes, and the status quo in mathlib 4 is that you have to do the same thing with removing all the ↑, which is even worse because you can't tell which ones you need to remove!
Floris van Doorn (Nov 09 2023 at 16:11):
I would like to not see ⇑f x in my goal view at all. f x is unambiguous and (almost?) always parses correctly anyway. So it's just noise. (Ideally clicking on f in the goal view would show that there is a coercion.)
If ⇑f is not applied to an argument (e.g. ⇑f = ⇑g or F (⇑f)), then the ⇑ would be nice to see.
Floris van Doorn (Nov 09 2023 at 16:12):
The same applies to sorts, but they are a lot less frequent (in the formalizations I do), so I care less about that.
Eric Wieser (Nov 09 2023 at 16:13):
As an example of where you do need ⇑, consider the goal state ⇑f + ⇑g = ⇑(f + g)
Eric Wieser (Nov 09 2023 at 16:16):
It's not immediately clear to me what the heuristic here is; the problem in general is when you have some polymorphic function h (here HAdd.hAdd) such that both h ⇑f ⇑g and ⇑(h f g) are well-typed.
Floris van Doorn (Nov 09 2023 at 16:17):
I covered that case in my message, and I do want to see the ⇑ (or ↑) there, indeed.
Eric Wieser (Nov 09 2023 at 16:17):
I'm not sure that helps with (⇑f + ⇑g) x = ⇑(f + g) x
Eric Wieser (Nov 09 2023 at 16:18):
Ah yes, that sounds sufficient, you'd see (⇑f + ⇑g) x = (f + g) x
Floris van Doorn (Nov 09 2023 at 16:18):
The heuristic I was thinking of: is the ⇑f applied to another argument. There might be a few cases where the ⇑ is still superfluous, like Function.curry ⇑f, but I'm happy to accept those.
Eric Wieser (Nov 09 2023 at 16:40):
std4#354 implements that heuristic
Floris van Doorn (Nov 09 2023 at 16:57):
Eric Wieser said:
std4#354 implements that heuristic
Did you forget to push new commits?
/-- info: ⇑f 1 : Nat-/
Eric Wieser (Nov 09 2023 at 16:59):
$ git commt -m "implement Floris' heuristic"
git: 'commt' is not a git command. See 'git --help'.
Whoops, yes
Eric Wieser (Nov 09 2023 at 17:00):
I guess we need to settle the precedence discussion before that it safe to merge; I don't really care much about the outcome of #8297, and was just following Mario's advice
Floris van Doorn (Nov 09 2023 at 17:06):
I understand. I also don't care too much in #8297, and would definitely not block it, but when looking at the changes I was wondering whether it was actually a regression.
It's counterintuitive that ↑ and ⇑ have different precedences, and maybe that weighs stronger than a few extra parentheses.
Eric Wieser (Nov 09 2023 at 17:08):
Hot take: ↑ has the wrong precedence, and should be just shy of = so that (↑a + b : X) means "coerce a + b to X", since that's nicer than the (↑(a + b) : X) we currently have to write
Floris van Doorn (Nov 09 2023 at 17:11):
Thinking seriously about your message makes me agree with Mario's suggestion. :sweat_smile:
Yes, low-precedence ↑ would probably lead to fewer parentheses, but a prefix operation with such a low precedence is just evil.
Mario Carneiro (Nov 09 2023 at 17:28):
Unfortunately, we have very many low precedence prefix operators because most macros are like that. Another one with an evil precedence is !:
example (f : Bool → Bool → Bool) (x : Bool) : (f x !x = x) = (f x !(x = x)) := rfl
Eric Wieser (Nov 09 2023 at 17:34):
That's surely a bug, right?
Jireh Loreaux (Nov 09 2023 at 17:47):
eww, gross.
Martin Dvořák (Nov 09 2023 at 18:25):
Eric Wieser said:
Hot take:
↑has the wrong precedence, and should be just shy of=so that(↑a + b : X)means "coercea + btoX", since that's nicer than the(↑(a + b) : X)we currently have to write
Your suggestion would essentially guarantee that my brain would experience a parse error every time ↑a + b appears in front of me. I understand your desire to do coërcion as the last step, but the syntax would have to be something like (a + b ↑: X) to preserve the last drops of my sanity. A prefix operator, especially one that is typically written without a space, must bind tightly.
Martin Dvořák (Nov 09 2023 at 18:27):
Mario Carneiro said:
Unfortunately, we have very many low precedence prefix operators because most macros are like that. Another one with an evil precedence is
!:example (f : Bool → Bool → Bool) (x : Bool) : (f x !x = x) = (f x !(x = x)) := rfl
My disgust is immeasurable, and my day is ruined. Please somebody PR a fix asap.
Eric Wieser (Nov 13 2023 at 15:25):
Eric Wieser said:
std4#354 implements that heuristic
Just to be explicit, the behavior in that PR is now:
variable (f : WrappedFun Nat) (g : Nat → WrappedFun Nat) (h : WrappedFun (WrappedFun Nat))
/-- info: ⇑f : Nat → Nat -/
#guard_msgs in #check f.fn
/-- info: ⇑f : Nat → Nat -/
#guard_msgs in #check ⇑f
-- applied functions do not need the `⇑`
/-- info: f 1 : Nat -/
#guard_msgs in #check ⇑f 1
/-- info: ⇑(g 1) : Nat → Nat -/
#guard_msgs in #check ⇑(g 1)
/-- info: (g 1) 2 : Nat -/
#guard_msgs in #check g 1 2
/-- info: ⇑h : Nat → WrappedFun Nat -/
#guard_msgs in #check ⇑h
/-- info: h 1 : WrappedFun Nat -/
#guard_msgs in #check h 1
/-- info: ⇑(h 1) : Nat → Nat -/
#guard_msgs in #check ⇑(h 1)
/-- info: (h 1) 2 : Nat -/
#guard_msgs in #check h 1 2
Eric Wieser (Nov 21 2023 at 15:36):
This is now live, see docs#FunLike.coe_injective for a place where the new arrow appears in place of the old one, and docs#FunLike.ext_iff for a place where it now is eliminated.
Eric Wieser (Nov 21 2023 at 15:36):
(hmm, this doesn't seem to have affected the docs)
Last updated: May 02 2025 at 03:31 UTC