dissoc

Optimizing syntax-quote

Syntax-quote in Clojure is an expressive reader macro for constructing syntax. The library backtick demonstrates how to write syntax-quote as a macro. Both approaches are ripe for optimization—take these programs that both evaluate to []:

'`[]
;=> (clojure.core/apply
;     clojure.core/vector
;     (clojure.core/seq (clojure.core/concat)))

(macroexpand-1 '(backtick/syntax-quote []))
;=> (clojure.core/vec (clojure.core/concat))

The reason syntax-quote’s expansion is so elaborate comes down to unquote-splicing (~@) support. When the program passed to ~@ is completely dynamic, the extra computation is essential.

(macroexpand-1 '(backtick/syntax-quote [1 ~@a 4]))
;=> (clojure.core/vec (clojure.core/concat [(quote 1)] a [(quote 4)]))

Since a cannot be evaluated at compile-time, we can’t do much better than this in terms of code size. The problem is that we’re stuck with this extra scaffolding even when ~@ is never used. We don’t even need it for unquote (~):

(macroexpand-1 '(backtick/syntax-quote [1 ~two ~three 4]))
;=> (clojure.core/vec
;     (clojure.core/concat
;       [(quote 1)] [two] [three] [(quote 4)]))

A more direct expansion would be ['1 two three '4].

I have implemented a branch of backtick that optimizes syntax-quote to only pay for the penalty of ~@ if it is used.

You can see the progression of the generated program becoming more dynamic as less static information can be inferred.

(macroexpand-1 '(backtick/syntax-quote []))))
;=> []
(macroexpand-1 '(backtick/syntax-quote [~local-variable]))))
;=> [local-variable]
(macroexpand-1 '(backtick/syntax-quote [~@local-variable]))))
;=> (clojure.core/vec local-variable)

Future work includes flattening spliced collections, such as:

(macroexpand-1 '(backtick/syntax-quote [1 ~@[two three] 4]))
;=> (clojure.core/vec
;     (clojure.core/concat [(quote 1)] [two three] [(quote 4)]))

This should be simply ['1 two three '4].

PR’s to my branch are welcome for further performance enhancements.

Also, if you are interested in implementing these changes in a real Clojure implementation, jank is accepting contributions. It will directly help with ongoing efforts towards AOT compilation:

20 Apr 2025