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 "coerce a + b to X", 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: Dec 20 2023 at 11:08 UTC