Zulip Chat Archive
Stream: lean4
Topic: pre-RFC: pretty printer option for numeric literal types
Kyle Miller (Nov 29 2023 at 19:05):
In the Lean 3 community edition, we had the option pp.numeralTypes
that would cause numeric literals to pretty print as (2 : Nat)
or (2 : Real)
. This is useful when working with heterogenous operators like ^
, since you can see at a glance why a lemma might not apply to a term such as x ^ 2
(is it x ^ (2 : Nat)
or x ^ (2 : Real)
?)
- What should the Lean 4 name of the option be? "Numeral" isn't a word used to describe numeric literals, so it might not be discoverable. Some options are
pp.numTypes
,pp.numLitTypes
, andpp.numericTypes
. - What should be the scope of a "number"? While we could pretty print only
@OfNat.ofNat X 2 _
as(2 : X)
, for clarity we could take a wider notion of "number". Followingnorm_num
, we could have a "number" be a natural number, integer, or rational number literal (a natural number is anOfNat.ofNat
literal, an integer is either a natural number or negative of one, and a rational is an integer or a quotient of an integer and a natural). This way, we could see(-2 : Int)
and(5/4 : Rat)
, rather than-(2 : Int)
and(5 : Rat) / (4 : Rat)
. - Should raw natural number literals be affected? I'm thinking "no". Perhaps we could have
pp.natLit
to pretty print them asnat_lit 2
. - Should the option have more than two settings? A third setting could be to pretty print types for everything but
Nat
. That way we get(2 : Fin 5)
rather than(2 : Fin (5 : Nat))
. We rejected this sort of complexity in the community edition, and I'm leaning toward "no" for a first version. - The community edition also had a feature where you could mark types with the
pp_numeral_type
attribute. That way, you could for example doattribute [local pp_numeral_type] Fin
to causeFin
literals to print with a type ascription. That way, you get(2 : Fin 5)
rather than(2 : Fin (5 : Nat))
. This does not need to be part of the RFC however since it can be implemented by users easily enough as a custom delaborator (e.g., it could be implemented in std or mathlib)
Patrick Massot (Nov 29 2023 at 19:42):
I think all names in 1. are equally discoverable since I will use auto-completion starting with pp.num
in all cases. So I would go for the most explicit description pp.numericTypes
. 2. I like (-2 : Int)
and (5/4 : Rat)
(especially the second one). 3. I think I should never see a raw literal, so I like nat_lit 2
that clearly shows the issue. 4. (2 : Fin (5 : Nat))
brings nothing, right? 5. No opinion
Kyle Miller (Nov 29 2023 at 20:16):
It would be cool if there were a way to detect whether we were pretty printing something that's in a polymorphic context or not. That way we could print Fin 2
rather than Fin (2 : Nat)
since the argument to Fin
is always Nat
. I believe this could be implemented by having the app
delaborator set pp.numericTypes
to false
for such arguments, but this would add complexity for not too much benefit.
Mac Malone (Nov 29 2023 at 20:49):
I think the best name for (1) depends on how (2) and (5) are resolved. If it is just pretty printing ofNat
, a name like pp.numLitTypes
makes more sense. If it also doing (2) -- which I think would be wonderful -- then pp.numericTypes
sounds better.
Eric Wieser (Nov 29 2023 at 22:58):
Was this supposed to be handled automatically by pp.analyze
?
Mario Carneiro (Nov 29 2023 at 23:13):
pp.analyze
does not use type ascriptions, it would pass implicits if necessary
Eric Wieser (Nov 29 2023 at 23:36):
pp.ofNat.withType true
to match pp.proofs.withType
?
Mac Malone (Nov 30 2023 at 03:50):
@Eric Wieser A wrinkle is that pp.ofNat
is not an option that this is modifying unlike with pp.proofs
.
Thomas Murrills (Nov 30 2023 at 21:53):
Maybe it would be simpler to create a generic attribute-based means of deciding when to add type ascriptions. This would allow us to offload the number-specific infrastructure from core to std4 and mathlib4, and also enable it to be extensible to new notions of numbers.
One way to do it would be to have a pp.types
group, and then register options in this group. The core functionality would consist of looking for options in this group, checking the extension, and applying whatever functions it found to the data at hand. A possible API might consist of writing something like @[type_ascription numeric] def shouldAnnotate (e eType : Expr) (headAndPriorArgs : Option (Expr × Array Expr)) : DelabM Bool := ...
; when pp.types.numeric
was true
, the core delaborators would pass {the expression at hand, its type, the head of the expression it appeared within (if there was one) and any prior arguments to that head} to shouldAnnotate
and any other functions tagged with @[type_ascription numeric]
. If any of them returned true
, we'd annotate it with the type. (It would do this for all registered options in the pp.types
group.)
The implementation in core would be generic and simple, and all of the specific maintenance and design choices could be performed outside of core.
Thomas Murrills (Nov 30 2023 at 21:53):
(Btw, in this version, I'm imagining that an implementation could use the headAndPriorArgs
to detect polymorphism if we wanted to. This would open up type ascriptions in other contexts down the road, like annotating only certain things, e.g. Ring
s: we could detect if any of the prior args were instances of Ring R
, and if eType
were R
, we could annotate it. Maybe there's a better implementation than passing the head and prior args—not too familiar with what info is available in DelabM
—but just wanted to introduce the shape of the idea.)
Kyle Miller (Nov 30 2023 at 22:16):
One of the features I would like pp.numericTypes
to have is that we would see (-2 : Int)
and (5 / 4 : Rat)
, which I don't see this system being able to support.
However, this does point toward a way to implement another feature that I want. The pretty printing for Exists
doesn't show binder types, unlike in foralls. The underlying problem is that Exists
uses an app_unexpander
, and so it relies on binder types being included for fun
s. If there were a @[pre_delab]
attribute that could give "pre-delaborators" a chance to look at an expression and annotate it with pretty printing options (including inserting mdata expressions), then there could be a pre-delaborator that annotates the last argument to Exists
with pp.funBinderTypes
. This concept of pre-delaborators seems to also be a way to implement your idea.
Something else this could be useful for is to solve the problem that affects overapplied functions that have app_unexpander
s, since the pre-delaborator could take f x1 ... xm y1 ... yn
and wrap f x1 ... xm
in a mdata expression, which would let the app_unexpander
see a function with exactly m arguments and then let the standard app delaborator handle the remaining n.
Kyle Miller (Nov 30 2023 at 22:18):
This has some overlap with pp.analyze
, but I'm imagining pre-delaborators to act locally, right before a sub-expression is actually delaborated.
Kyle Miller (Nov 30 2023 at 22:21):
Mathlib features such as pp_univ
could be implemented using pre-delaborators. This delaborator could be one too.
Thomas Murrills (Nov 30 2023 at 23:09):
Kyle Miller said:
One of the features I would like
pp.numericTypes
to have is that we would see(-2 : Int)
and(5 / 4 : Rat)
, which I don't see this system being able to support.
Why would it not be able to support this? If we saw an expression like 5 / 4
of type Rat
, shouldAnnotate
would return true
, and we would simply insert a type annotation, right?
Thomas Murrills (Nov 30 2023 at 23:16):
(Separately, I do like the notion of pre-delaborators inserting mdata, though. :) )
Eric Wieser (Nov 30 2023 at 23:20):
You also have to not mark the 5
and 4
inside as suitable for annotation
Thomas Murrills (Nov 30 2023 at 23:39):
That seems doable, I think, seeing as we would have full access to the expression we’re inside via headAndPriorArgs
, right? And so we could tell whether we were viewing a natural number inside one of these expressions. (Does the DelabM
state tell us about the expression we’re in too? Note that shouldAnnotate
lands in DelabM Bool
.)
Kyle Miller (Dec 05 2023 at 02:06):
This is now an RFC: #3021
The pp.numericTypes
is like what we discussed here, but I added pp.natLit
for pretty printing raw natural number literals with the nat_lit
prefix. If pp.natLit
is not set, then setting pp.numericTypes
enables pp.natLit
.
Eric Wieser (Dec 05 2023 at 13:22):
Eric Wieser (Dec 05 2023 at 13:23):
should natLit
just be on by default?
Last updated: Dec 20 2023 at 11:08 UTC