hy icon indicating copy to clipboard operation
hy copied to clipboard

Nested unquotes leave behind extra unquote forms

Open Kodiologist opened this issue 2 years ago • 2 comments

In light of #2251, consider

(setv c '(f))
```[~a ~~b ~~~c]

This returns

``[~a ~~b ~~(f)]

but a more useful, and perhaps more logical, behavior would be

``[~a ~~b (f)]

It's hard to say exactly what should happen in these cases because we usually think of nested backticks with intervening unquotes. Nested backticks without an intervening unquote only come up when you're trying to return a backticked form for later expansion.

Kodiologist avatar Mar 21 '22 16:03 Kodiologist

I took a survey of Lisps to see if there was a standard approach to this issue, and it looks like there isn't a great deal of consensus. Emacs and Scheme keep the extra unquotes like Hy does, whereas Common Lisp and Clojure get rid of them.

Emacs Lisp:

(let ((c 17))
  (nth 1 (nth 1 ```(,a ,,b ,,,c))))
=> ((\, a) (\, (\, b)) (\, (\, 17)))

Common Lisp:

(let ((c 17)) (princ
  ```(,a ,,b ,,,c)))
=> (LIST 'LIST 'A B 17)

Chicken Scheme:

(let ((c 17)) (print
  ```(,a ,,b ,,,c)))
=> (quasiquote (quasiquote ((unquote a) (unquote (unquote b)) (unquote (unquote 17)))))

Clojure:

(ns x (:require [clojure.pprint :as pp]))

(let [c 17] (pp/pprint
  ```[~a ~~b ~~~c 18]))
=> (clojure.core/seq
 (clojure.core/concat
  (clojure.core/list 'clojure.core/apply)
  (clojure.core/list 'clojure.core/vector)
  (clojure.core/list
   (clojure.core/seq
    (clojure.core/concat
     (clojure.core/list 'clojure.core/seq)
     (clojure.core/list
      (clojure.core/seq
       (clojure.core/concat
        (clojure.core/list 'clojure.core/concat)
        (clojure.core/list
         (clojure.core/seq
          (clojure.core/concat
           (clojure.core/list 'clojure.core/list)
           (clojure.core/list 'x/a))))
        (clojure.core/list
         (clojure.core/seq
          (clojure.core/concat
           (clojure.core/list 'clojure.core/list)
           (clojure.core/list x/b))))
        (clojure.core/list
         (clojure.core/seq
          (clojure.core/concat
           (clojure.core/list 'clojure.core/list)
           (clojure.core/list 17))))
        (clojure.core/list
         (clojure.core/seq
          (clojure.core/concat
           (clojure.core/list 'clojure.core/list)
           (clojure.core/list 18))))))))))))

Kodiologist avatar Apr 04 '22 19:04 Kodiologist

I think that our code for rendering quoted forms (render_quoted_form) is not quite clever enough, because it doesn't give backticks sufficiently special treatment. The Common Lisp HyperSpec says

If the backquote syntax is nested, the innermost backquoted form should be expanded first. This means that if several commas occur in a row, the leftmost one belongs to the innermost backquote.

and I suspect that's the best way to do it.

Here are some more comparisons of Common Lisp with Hy. Remember that hy.repr always shows one more quote level than Common Lisp's printing functions would, in order for the output to round-trip.

Common Lisp: ``,a    =>  A
Hy:          ``~a    =>  '`~a
Common Lisp: '`,a    =>  `,A
Hy:          '`~a    =>  '`~a
Common Lisp: ``,``a  =>  ''A
Hy:          ``~``a  =>  '`~``a

Kodiologist avatar Apr 04 '22 20:04 Kodiologist