Zulip Chat Archive
Stream: metaprogramming / tactics
Topic: Delaborator request
Heather Macbeth (Jan 07 2024 at 04:20):
I'd love to have a delaborator which pretty-prints quantified statements
- with type annotations
- without parentheses
- paired up when relevant
So, for example,
∃ a b : Nat, ∀ c : Nat, a + b = c
In the standard setup this pretty-prints as
∃ a b, ∀ (c : Nat), a + b = c
and with set_option pp.funBinderTypes true
this pretty-prints as
∃ (a : Nat), ∃ (b : Nat), ∀ (c : Nat), a + b = c
Kyle Miller (Jan 07 2024 at 23:12):
Something I'm hoping we can find a general solution for is giving pretty printer hints for unexpanders. Generally notations using binders use a lambda function, which means whether they get to pretty print with a binder is at the mercy of whether pp.funBinderTypes
is set.
Here's hack that we can use on a case-by-case basis to ensure binder types. This can be generalized to be a command to attach binder type information to a particular argument.
import Mathlib.Lean.PrettyPrinter.Delaborator
open Lean PrettyPrinter.Delaborator SubExpr in
/-- "Pre-delaborator" for `Exists` that adds a `funBinderTypes` hint to its last argument. -/
@[delab app.Exists]
def preDelabExists : Delab :=
whenPPOption getPPNotation <|
whenPPOption getPPPiBinderTypes <|
whenNotPPOption getPPAnalysisSkip <|
withOptionAtCurrPos `pp.analysis.skip true do
let e ← getExpr
guard <| e.isAppOfArity ``Exists 2
let optionsPerPos ← withAppArg do
return (← read).optionsPerPos.setBool (← getPos) pp.funBinderTypes.name true
withTheReader Context ({· with optionsPerPos}) delab
#check ∃ x, x = 0
/-
∃ (x : ℕ), x = 0 : Prop
-/
-- Maybe this should be its own pp option, but I hooked it into `piBinderTypes`
-- since it seems to me that you want to control binders for everything quantifier-like.
set_option pp.piBinderTypes false in
#check ∃ x, x = 0
/-
∃ x, x = 0 : Prop
-/
-- The hack avoids setting `pp.funBinderTypes` globally:
#check fun x => x + 1
/-
fun x ↦ x + 1 : ℕ → ℕ
-/
Kyle Miller (Jan 07 2024 at 23:14):
The parentheses in the type ascription come from the core app unexpander. If you paste this version in, you can see it without parens:
@[app_unexpander Exists] def unexpandExists' : Lean.PrettyPrinter.Unexpander
| `($(_) fun $x:ident => ∃ $xs:binderIdent*, $b) => `(∃ $x:ident $xs:binderIdent*, $b)
| `($(_) fun $x:ident => $b) => `(∃ $x:ident, $b)
| `($(_) fun ($x:ident : $t) => $b) => `(∃ $x:ident : $t, $b) -- removed parens
| _ => throw ()
Heather Macbeth (Jan 08 2024 at 05:22):
@Kyle Miller Thanks for looking at this! So to check that I'm following your directions: If I now use your
@[delab app.Exists] def preDelabExists : Delab := ...
@[app_unexpander Exists] def unexpandExists' : Lean.PrettyPrinter.Unexpander := ...
and set set_option pp.piBinderTypes true
, then I get a single existential to pretty-print exactly as I wanted:
#check ∃ x : Nat, x = 0 -- ∃ x : Nat, x = 0
But double existentials still don't get merged:
#check ∃ x y : Nat, x = y -- ∃ x : Nat, ∃ y : Nat, x = y
and universal quantifiers still don't hide their parentheses:
#check ∀ x : Nat, x = 0 -- ∀ (x : Nat), x = 0
Is this what you would expect?
Kyle Miller (Jan 08 2024 at 05:28):
Yes, that's right (with the understanding that piBinderTypes is true by default)
Last updated: May 02 2025 at 03:31 UTC