Zulip Chat Archive
Stream: lean4
Topic: adding ellipsis to a term
Patrick Massot (Jul 23 2024 at 13:53):
I have some syntax x : term
and I would like to create the syntax x ..
adding an ellipsis. This works as I would expect it only sometimes. If x
is already a Term.app
, my naive attempt creates a nested Term.app
instead of a single app with multiple arguments. How can I do this reliably?
Patrick Massot (Jul 23 2024 at 13:57):
If this is not clear enough, here is a mwe:
variable (f : Nat → Nat → Nat)
elab "do_nothing" x:term : command => do
dbg_trace x
elab "add_ellipsis" x:term : command => do
let y ← `($x ..)
dbg_trace y
add_ellipsis f
do_nothing f ..
add_ellipsis f 1
do_nothing f 1 ..
Patrick Massot (Jul 23 2024 at 13:58):
My hope would be to have add_ellipsis f 1
and do_nothing f 1 ..
to return the same thing, just as add_ellipsis f
and do_nothing f ..
return the same thing.
Damiano Testa (Jul 23 2024 at 22:05):
I'm not at a computer, but I wonder if you should push the ..
at the end of the x.getArgs
array, instead of using the `()
constructor for the syntax.
Eric Wieser (Jul 24 2024 at 00:06):
Is there a reason you care about the difference here? One reason to prefer the nested .app
is to distinguish the application in the source code from the the one you created synthetically
Kyle Miller (Jul 24 2024 at 16:18):
Maybe jumping a head a bit, you can make a term elaborator for this:
import Lean
open Lean Elab Term
def expandApp' (stx : Syntax) : MetaM (Syntax × Array NamedArg × Array Arg × Bool) := do
if stx.isOfKind ``Parser.Term.app then
expandApp stx
else
return (stx, #[], #[], false)
syntax (name := addEllipsis) "add_ellipsis" term : term
@[term_elab addEllipsis]
def elabAddEllipsis : TermElab
| `(add_ellipsis $x) => fun expectedType? => do
let (f, namedArgs, args, _ellipsis) ← expandApp' x
elabAppArgs (← elabTerm f none) namedArgs args expectedType? (explicit := false) (ellipsis := true)
| _ => fun _ => throwUnsupportedSyntax
variable (f : Nat → Nat → Nat)
#check add_ellipsis f -- f ?m.1103 ?m.1104 : Nat
#check add_ellipsis f 1 -- f 1 ?m.1122 : Nat
#check add_ellipsis f 1 2 -- f 1 2 : Nat
Kyle Miller (Jul 24 2024 at 16:27):
There's probably a cleaner way to do it, but here's a function that tacks on ..
if it's not already present.
import Lean
open Lean Elab Term
def withEllipsis (stx : Term) : Term := Unhygienic.run <| withRef stx do
if stx.raw.isOfKind ``Parser.Term.app then
if stx.raw[1].getArgs.back.isOfKind ``Parser.Term.ellipsis then
return stx
else
let ellipsis ← `(Parser.Term.ellipsis| ..)
return ⟨stx.raw.modifyArg 1 fun arg => arg.modifyArgs fun args => args.push ellipsis⟩
else
`($(⟨stx⟩) ..)
macro "add_ellipsis " x:term : term => return withEllipsis x
variable (f : Nat → Nat → Nat)
#check add_ellipsis f -- f ?m.1103 ?m.1104 : Nat
#check add_ellipsis f 1 -- f 1 ?m.1122 : Nat
#check add_ellipsis f 1 2 -- f 1 2 : Nat
Thomas Murrills (Jul 29 2024 at 21:02):
Nice! I recently had to make a (repeated) syntax app myself; I opted for nested apps, but maybe it’s worth having e.g. Term.mkAppN
(and Term.mkApp
) somewhere which does it this way.
Last updated: May 02 2025 at 03:31 UTC