gelisam / klister

an implementation of stuck macros
BSD 3-Clause "New" or "Revised" License
134 stars 11 forks source link

add unquote-splicing (,@) support to quasiquote #186

Open gelisam opened 1 year ago

gelisam commented 1 year ago

I much prefer to use quasiquote than close-syntax, but I often have to use close-syntax to append a (List Syntax) at the end of a list-contents. Being able to write

`(list ,@xs)

instead of

(close-syntax stx stx
  (list-contents (:: 'list xs))

would improve a lot of code.

In terms of implementation, this requires adding a new syntactic sugar, so that ,@ desugars to unquote-splicing, and to tweak the quasiquote.kl implementation to give it the right semantics.

gelisam commented 1 year ago

The above is a missing feature in quasiquote, but I have now also encountered a bug in quasiquote:

-- should result in '(1 2 `(3 ,'4)), instead results in '(1 2 `(3 4))
(example
  `(1 2 `(3 ,'4)))
david-christiansen commented 1 year ago

Why wouldn't `,'4 be 4?

gelisam commented 1 year ago
`,'4

should indeed result in '4, but under a nested quasiquote,

``,'4

should result in

`,'4

At least, that's the behaviour in Racket:

$ racket
Welcome to Racket v7.4.
> `,'4
4
> ``,'4
'`,'4

See the "A quasiquote form within the original datum increments the level of quasiquotation" section of Racket's quasiquote documentation.

I think Racket's behaviour makes a lot of sense. It allows macros to splice-in unknown expressions inside a quasiquote without having to worry about whether the unknown expression itself contain a quasiquote. For example, suppose a macro verbose wraps its input inside a quasiquote:

(verbose (+ 2 2))
=>
`(the expression (+ 2 2) equals ,(+ 2 2))
=>
'(the expression (+ 2 2) equals 4)

then consider what happens when verbose is given an input which contains a quasiquote:

(verbose `(two plus two is ,(+ 2 2)))
=>
`(the expression `(two plus two is ,(+ 2 2)) equals ,`(two plus two is ,(+ 2 2)))
=>
?

with Racket's behaviour, the result is the one I expect:

`(the expression `(two plus two is ,(+ 2 2)) equals ,`(two plus two is ,(+ 2 2)))
=>
`(the expression `(two plus two is ,(+ 2 2)) equals (two plus two is 4))

whereas with the alternative behaviour, I get a different result:

`(the expression `(two plus two is ,(+ 2 2)) equals ,`(two plus two is ,(+ 2 2)))
=>
`(the expression `(two plus two is 4) equals (two plus two is 4))

I encountered exactly that problem: I was very confused as to why

(define-syntax-rule (example-macro-syntax action)
  (group
    (define-macro (my-macro)
      (do (stx <- action)
          (pure `(quote ,stx))))
    (example (my-macro))))

was complaining that stx was not in scope, and it turned that the problem was that define-syntax-rule is implemented in a similar way to verbose, so its quasiquote is trying to evaluate ,stx much earlier than I expected.