Zulip Chat Archive

Stream: lean4

Topic: Missing Unexpanders for Core Functions?


Mac (May 15 2021 at 20:09):

I noticed that Lean is missing unexpanders for a lot of basic functions. For example, Array.empty and Array.push. Is there any reasons why defining unexpanders like the following would be a problem?

@[appUnexpander Array.empty]
def unexpandArrayEmpty : Lean.PrettyPrinter.Unexpander
| `($_f:ident) => `(#[])
| _  => throw ()

@[appUnexpander Array.push]
def unexpandArrayPush : Lean.PrettyPrinter.Unexpander
| `($_f:ident #[] $x) => `(#[$x])
| `($_f:ident #[$xs,*] $y) => `(#[$xs,*, $y])
| _  => throw ()

Daniel Selsam (May 15 2021 at 20:16):

@Mac What problem are you trying to solve? Notation already unexpands nicely. Why would you want to unexpand pushes that way?

Daniel Selsam (May 15 2021 at 20:25):

Note: Array notation is defined in terms of List:

set_option pp.notation false
#check (#[] : Array Nat) -- List.toArray List.nil : Array Nat
#check (#[1] : Array Nat) -- List.toArray (List.cons 1 List.nil) : Array Nat

Mac (May 15 2021 at 20:27):

The goal is to pretty-print sequence of pushes. The reason why this is relevant is that sequences of pushes are often used to construct arrays in some macro code (and such arrays end up being less than pretty).

Daniel Selsam (May 15 2021 at 20:28):

how about reducing the result explicitly, rather than having the pretty-printer do it?

Mac (May 15 2021 at 20:29):

Maybe Array is a bad example. A better example of something that does not get pretty printed ever is Name:

#check `foo.bar
/-
  Name.mkStr (Name.mkStr Name.anonymous "foo") "bar" : Name
-/

I would hope it would be pretty-printed as `foo.bar .

Mac (May 15 2021 at 20:30):

Also, fyi, using #reduce on an Array doesn't result in a pretty literal either:

#reduce #[2, 3]
/-
 { data := [2, 3] }
-/

Daniel Selsam (May 15 2021 at 20:32):

I don't know why the devs decided to represent Array literals this way, but given that decision, I think it would be confusing to have different representations of arrays that all pretty-print the same way.

Daniel Selsam (May 15 2021 at 20:33):

I meant reducingconverting the array to whatever the canonical representation is, which is currently the List.toArray <list> representation.

Mac (May 15 2021 at 20:36):

Daniel Selsam said:

I meant reducingconverting the array to whatever the canonical representation is, which is currently the List.toArray <list> representation.

Unfortunately, that is often not possible. Many built-in pieces use the push representation and thus errors are not very pretty. For example:

open Lean Syntax in
def badArrayQuote : Syntax -> Syntax
  := fun _ => `( #[2, 3] )
/-
  type mismatch
    do
      let info ← MonadRef.mkInfoFromRefPos
      let scp ← getCurrMacroScope
      let mainModule ← getMainModule
      pure
          (node (Name.mkStr Name.anonymous "term#[_,]")
            (Array.push
              (Array.push (Array.push Array.empty (atom info "#["))
                (node (Name.mkStr Name.anonymous "null")
                  (Array.push
                    (Array.push
                      (Array.push Array.empty
                        (node (Name.mkStr Name.anonymous "numLit") (Array.push Array.empty (atom info "2"))))
                      (atom info ","))
                    (node (Name.mkStr Name.anonymous "numLit") (Array.push Array.empty (atom info "3"))))))
              (atom info "]")))
  has type
    ?m.1784 Syntax : Type
  but is expected to have type
    Syntax : Type
-/

Mac (May 15 2021 at 20:37):

In fact, the above is a great example of why both Array and Name unexpanders would be quite desirable.

Mac (May 15 2021 at 20:38):

The ones I've written on my own produce the following:

open Lean Syntax in
def prettyBadArrayQuote : Syntax -> Syntax
  := fun _ => `( #[2, 3] )

/-
type mismatch
  do
    let info ← MonadRef.mkInfoFromRefPos
    let scp ← getCurrMacroScope
    let mainModule ← getMainModule
    pure
        (node `«term#[_,]»
          #[atom info "#[",
            node `«null» #[node `«numLit» #[atom info "2"], atom info ",", node `«numLit» #[atom info "3"]],
            atom info "]"])
has type
  ?m.51556 Syntax : Type
but is expected to have type
  Syntax : Type
-/

which is much cleaner in my opinion.

Daniel Selsam (May 15 2021 at 20:43):

That is a good example, thanks. I agree it would be nice for these to pretty-print as they do with your proposed unexpanders. I am still a little concerned about having different Array representations all rendering the same way though. Perhaps the Array.push representation can become the standard?

Mac (May 15 2021 at 20:49):

I can understand the qualms with having the toArray and push print the same way.

However, I would note that Array.empty and List.toArray [] are defeq:

example : (Array.empty : Array a) = List.toArray [] := rfl

So I don't think there would be as much as problem for them to print the same way.

Mac (May 15 2021 at 21:11):

For my particular example (quoting), I think the pretty printing issues boil down to the fact that the Quote instance for Array and Name don't use the current canonical representation of those types (Array uses empty/push instead of toArray and Name uses mkStr instead of a name literal). This could be resolved by either changing the current canonical representation to match Quote or changing Quote to match the current canonical representation.

Sebastian Ullrich (May 16 2021 at 07:41):

Yeah, I'm not satisfied with the pretty printed macro output either. Printing defeq things the same way is not a good idea though since tactics like simp don't work modulo defeq. Probably we should just move List.toArray to Prelude so we can use it in the quotation implementation.

Mario Carneiro (May 16 2021 at 10:32):

Printing defeq things the same way is not a good idea though since tactics like simp don't work modulo defeq.

Also there could be a performance or even behavior difference if one is a implementedBy constant and the other is the lean model (which is the case here re: Array.empty vs List.toArray [])

Sebastian Ullrich (May 16 2021 at 10:35):

List.toArray actually is the faster one here (if everything is inlined) because it uses Array.mkEmpty with an appropriate capacity


Last updated: Dec 20 2023 at 11:08 UTC