Zulip Chat Archive

Stream: general

Topic: Delaborator for MyList


Asei Inoue (Jul 03 2025 at 18:16):

In the following code, I define MyList by mimicking the standard List, and also define syntax for list literals.
I would like to improve the readability of the output of the #check and #eval commands.
To do this, I need to implement a delaborator and a Repr instance.
Since it's fine for the outputs of #check and #eval to be the same, I want to unify the implementation as much as possible.
Is it possible to derive the Repr instance from the implementation of the delaborator?

/-- A custom-defined `List` -/
inductive MyList (α : Type) where
  /-- An empty list -/
  | nil
  /-- Add an element to the head of the list -/
  | cons (head : α) (tail : MyList α)
deriving DecidableEq

/-- An empty `MyList` -/
notation:max "m[]" => MyList.nil

/-- Add an element to a `MyList` -/
infixr:70 " ::: " => MyList.cons

/-- Custom list literal syntax. A trailing comma is allowed. -/
syntax "m[" term,*,? "]" : term

macro_rules
  | `(m[ ]) => `(m[])
  | `(m[$x]) => `($x ::: m[])
  | `(m[$x, $xs,*]) => `($x ::: (m[$xs,*]))

-- Since there is no delaborator, the custom syntax is not used in output.
-- We want it to display as `m[1, 2, 3] : MyList Nat`
#check m[1, 2, 3]

-- Since there is no `Repr` instance, the custom syntax is not used in output.
-- We want it to display as `m[1, 2, 3]`
#eval m[1, 2, 3]

Kyle Miller (Jul 03 2025 at 18:25):

You do not need a Repr instance for #eval. If you have a ToExpr instance plus a delaborator, then #eval will use those to pretty print the result.

Asei Inoue (Jul 03 2025 at 18:26):

@Kyle Miller Thanks. Can a common implementation be used for both the ToExpr instance and the delaborator?

Kyle Miller (Jul 03 2025 at 18:27):

No, the ToExpr instance is what enables using the delaborator.

Aaron Liu (Jul 03 2025 at 18:27):

Here's what a ToExpr instance can look like:

def foldr {α β} (l : MyList α) (init : β) (cons : α  β  β) := match l with
  | .nil => init
  | .cons head tail => cons head (foldr tail init cons)

instance {α} [Lean.ToExpr α] : Lean.ToExpr (MyList α) where
  toExpr l := foldr l (.app (.const ``MyList.nil []) (Lean.ToExpr.toTypeExpr α))
    (fun a acc => .app (.app (.app (.const ``MyList.cons []) (Lean.ToExpr.toTypeExpr α)) (Lean.ToExpr.toExpr a)) acc)
  toTypeExpr := .app (.const ``MyList []) (Lean.ToExpr.toTypeExpr α)

Kyle Miller (Jul 03 2025 at 18:28):

It can also look like deriving ToExpr

Aaron Liu (Jul 03 2025 at 18:28):

There's a deriving handler???

Aaron Liu (Jul 03 2025 at 18:28):

There is a deriving handler!

Kyle Miller (Jul 03 2025 at 18:30):

import Mathlib.Util.Notation3

/-- A custom-defined `List` -/
inductive MyList (α : Type) where
  /-- An empty list -/
  | nil
  /-- Add an element to the head of the list -/
  | cons (head : α) (tail : MyList α)
  deriving DecidableEq, Lean.ToExpr

-- optional ::: notation
/-- Add an element to a `MyList` -/
infixr:70 " ::: " => MyList.cons

notation3 "m[" (x", "* => foldr (a b => MyList.cons a b) MyList.nil) "]" => x

-- Now both pretty print:

#check m[1, 2, 3]

#eval m[1, 2, 3]

Asei Inoue (Jul 03 2025 at 18:30):

why you use notation3? notation3 is better than notation?

Kyle Miller (Jul 03 2025 at 18:31):

notation3 has a number of additional features, features that used to be in the Lean 3 notation command

Kyle Miller (Jul 03 2025 at 18:31):

Notice that you get a delaborator for this complex notation

Asei Inoue (Jul 03 2025 at 18:32):

Amazing. But is there a way to unify them without using notation3?

Kyle Miller (Jul 03 2025 at 18:33):

I personally don't see any reason to avoid notation3.

Kyle Miller (Jul 03 2025 at 18:34):

(Although the foldr syntax is odd. I had to copy it from the test file!)

Asei Inoue (Jul 03 2025 at 21:40):

Let me rephrase my question.
In the code below, a delaborator is defined.
Using the output of this delaborator, is it possible to define a ToString or Repr instance?

/-- A custom-defined `List` -/
inductive MyList (α : Type) where
  /-- An empty list -/
  | nil
  /-- Add an element to the head of the list -/
  | cons (head : α) (tail : MyList α)
deriving DecidableEq

/-- An empty `MyList` -/
notation:max "m[]" => MyList.nil

/-- Add an element to a `MyList` -/
infixr:70 " ::: " => MyList.cons

/-- Custom list literal syntax. A trailing comma is allowed. -/
syntax "m[" term,*,? "]" : term

macro_rules
  | `(m[ ]) => `(m[])
  | `(m[$x]) => `($x ::: m[])
  | `(m[$x, $xs,*]) => `($x ::: (m[$xs,*]))

namespace MyList

open Lean PrettyPrinter

@[app_unexpander MyList.cons]
def cons.unexpander : Unexpander := fun stx =>
  match stx with
  | `($(_) $head $tail) =>
    match tail with
    | `(m[]) => `(m[ $head ])
    | `(m[ $xs,* ]) => `(m[ $head, $xs,* ])
    | `() => `(m[ $head, $tail ])
    | _ => throw ()
  | _ => throw ()

#check (m[] : MyList Nat)
#check m[1, 2, 3]

end MyList

Last updated: Dec 20 2025 at 21:32 UTC