Zulip Chat Archive
Stream: new members
Topic: Josh Tilles, and a metaprogramming question
Josh Tilles (Feb 26 2026 at 18:21):
:wave: Hello all! I’m a software developer with a hobbyist interest in math-y topics, particularly interested in using Lean to work through the exercises and puzzles of various books I’m reading.
I think I’d like some help getting started with Lean.PrettyPrinter.Parenthesizer… The following code block provides the details and context for why I’m asking.
-- I’m tinkering with Lean to write up my solutions to the exercises in Raymond Smullyan’s *To Mock a Mockingbird*.
-- I’m trying to bring the code nearer to the book's conventions. I'd especially appreciate help on the printing side of things (like, in the proof state).
-- “A certain enchanted forest is inhabited by talking birds. Given any birds A and B, if you call out the name of B to A, then A will respond by calling out the name of some bird to you”
inductive Bird : Type where
| call : Bird → Bird → Bird
/-
“By a *mockingbird* is meant a bird M such that for any bird x, the following condition holds:
Mx = xx
M is called a mockingbird for the simple reason that its response to any bird x is the same as x’s response to itself—in other words, M *mimics* x as far as its response to x goes. This means that if you call out x to M or if you call out x to itself, you will get the same response in either case.”
-/
def Bird.is_a_mockingbird__v0 (M : Bird) : Prop :=
∀ x : Bird, M.call x = x.call x
-- Allow writing the bird equations more directly by treating the birds like functions:
instance : CoeFun Bird (fun _ => Bird → Bird) where
coe := Bird.call
def Bird.is_a_mockingbird (M : Bird) : Prop :=
∀ x : Bird, M x = x x
def Bird.is_a_bluebird (B : Bird) : Prop :=
∀ x y z : Bird, B x y z = x (y z)
def Bird.is_an_eagle (E : Bird) : Prop :=
∀ x y z w v : Bird, E x y z w v = x y (z w v)
-- A sample problem: prove than an eagle can be derived from a bluebird.
example : (∃ B : Bird, B.is_a_bluebird) → ∃ E : Bird, E.is_an_eagle :=
sorry -- solution omitted since it's not the emphasis of my current question
variable (B E a b c d e : Bird)
(hB : B.is_a_bluebird)
(hE : E.is_an_eagle)
-- The below example shows how the equations become unwieldy pretty quickly:
/--
info: hB a b : ∀ (z : Bird), ((B.call a).call b).call z = a.call (b.call z)
-/
#guard_msgs in
#check hB a b
-- I tried asking Claude and it directed me to the `app_unexpander` attribute.
@[app_unexpander Bird.call]
def unexpandBirdCall : Lean.PrettyPrinter.Unexpander
| `($_ $x $y) => ``($x $y)
| _ => throw ()
-- That results in a significant improvement!
/--
info: hB a b : ∀ (z : Bird), ((B a) b) z = a (b z)
-/
#guard_msgs in
#check hB a b
-- However, there are still more parentheses in the output than seem necessary.
-- I'll construct a slightly more complex example to illustrate the improvement I'd like to see.
/--
info: hE a b c d e : ((((E a) b) c) d) e = (a b) ((c d) e)
-/
#guard_msgs in
#check hE a b c d e
-- I’d like the printed output to reflect the convention of left-associativity.
-- hE a b c d e : E a b c d e = a b (c d e)
-- I’m guessing `Lean.PrettyPrinter.Parenthesizer` is relevant here but it's unclear to me how to get started applying it to the code thus far.
Kyle Miller (Feb 26 2026 at 20:19):
What's going on is that applications are Syntax objects, and they're supposed to include a list of all arguments that are part of that application. Having the function itself be an application necessitates parentheses since Lean will elaborate (f a b) c d slightly differently from f a b c d.
You can write an unexpander that looks for applications and merges them:
@[app_unexpander Bird.call]
def unexpandBirdCall : Lean.PrettyPrinter.Unexpander
| `($_ $x $ys*) =>
match x with
| `($x $xs*) => `($x $xs* $ys*)
| _ => `($x $ys*)
| _ => throw ()
/-- info: hB a b : ∀ (z : Bird), B a b z = a (b z) -/
#guard_msgs in
#check hB a b
/-- info: hE a b c d e : E a b c d e = a b (c d e) -/
#guard_msgs in
#check hE a b c d e
Last updated: Feb 28 2026 at 14:05 UTC