Backquotes - Lisp
Table of Contents
1. Introduction
The idea is we want a sort of "parametrized quotation" or "template", where we explicitly splice in lists or explicitly substitute in the values of variables.
Where a comma appears, the form following the comma is to be evaluated to produce an object to be inserted at that point. For example,
(let ((b 3)) `(a b ,b ,(+ b 1) b)) ;; => (a b 3 4 b)
If a comma is followed by @
(an at-sign), then the form following the
at-sign is evaluated to produce a list of objects; this list is then
"spliced" into place in the template. For example,
(let ((x '(a b c))) `(x ,x ,@x foo ,(cadr x) bar ,(cdr x) baz ,@(cdr x))) ;; => (x (a b c) a b c foo b bar (b c) baz b c)
This is especially useful for macros. For example, from my toy proof assistant:
(defmacro defthm (name params &body formula-body) `(progn (defun ,(intern (string name) 'cl-aim.fol.thm) ,params ,@formula-body) (export ',(intern (string name) 'cl-aim.fol.thm) 'cl-aim.fol.thm)))
We can splice in a sublist using ,@my-list
to splice in the contents
of my-list
into a backquoted expression.
2. Basic Rules
Common Lisp: The Language (§ 22.1.3) gives the following heuristic rules governing backquotes:
`basic
is the same as'basic
— i.e.,(quote basic)
— for any formbasic
which is not a list or general vector`,form
is the same asform
for anyform
provided the representation ofform
does not begin with either@
or.
`,@form
is an error`(x1 x2 x3 ... xn . atom)
may be interpreted to mean(append [x1] [x2] [x3] ... [n] (quote atom))
where the brackets are used to indicate a transformation of an \(x_{j}\) as follows:form
is interpreted as(list `form)
which contains a backquoted form thatmust then be further interpreted,form
is interpreted as(list form)
,@form
is simply interpreted asform
`(x1 x2 ... xn)
is interpreted as`(x1 x2 ... xn . nil)
and reduces to the previous case`(x1 x2 ... xn . ,form)
may be interpreted to mean`(append [x1] [x2] ... [xn] form)
where the brackets indicate a transformation of an \(x_{j}\) as described above.`(x1 x2 ... xn . ,@form)
is an error.`#(x1 x2 ... xn)
may be interpreted to mean(apply #'vector `(x1 x2 ... xn))
No other uses of comma are permitted.
The use of ,.
is similar to ,@
except that ,.
is destructive,
using (for example) nconc
instead of append
.
3. Nested Backquotes
Puzzle: We want to return a backquoted expression from a function, what do we do?
(This happens frequently in computer algebra systems or compilers.)
Solution: Double backquotes. What happens is that an "intermediate backquotation" will be returned.
Heuristic: The "innermost" comma will be evaluated, leaving the "outermost" comma in the returned "intermediate backquote".
Example of what a double backquote looks like:
(let ((b :foobar)) ``(,A ,,B ,,17)) ;; => `(,A ,:FOOBAR ,17)
We use double commas to produce an "expanded comma" expression. That is,
(let ((b 'spam)) ``(,A ,,B ,,17)) ;; => `(,A ,SPAM ,17)
We replace B
with its value (this "consumes" one comma, prefixing the
value with a comma).
3.1. Idiom: comma-quote-comma
If we want to produce a quoted literal in the result, for example if B
were bound to a symbol, and we wanted to quote that symbol in the
result, we would do something like:
(let ((b 'spam)) ``(,A ,',B ,,17)) ;; => `(,A ,'SPAM ,17)
This then evaluates the quote in the result — that is, when we use
this backquoted expression, the second term ,'SPAM
evaluates to SPAM
as a symbol.
3.2. Nested Splicing
(let ((b '(spam und eggs))) ``(,A ,@,B ,,17)) ;; => `(,A ,@(SPAM UND EGGS) ,17) (let ((b '(spam und eggs))) ``(,A ,,@B ,,17)) ;; => `(,A ,SPAM ,UND ,EGGS ,17) (let ((b '(spam und eggs))) ``(,A ,,B ,,17)) ;; => `(,A ,(SPAM UND EGGS) ,17) (let ((b '(spam und eggs))) ``(,A ,',B ,,17)) ;; => `(,A ,'(SPAM UND EGGS) ,17) (let ((b '(spam und eggs))) ``(,A ,@',B ,,17)) ;; => `(,A ,@'(SPAM UND EGGS) ,17)
4. Caution: Compiler Dependence
In SBCL 2.2.4.46-d4779571f, we see in the REPL:
;; SBCL (caadr (let ((b '(spam und eggs))) ``(,A ,@',B ,,17))) ;; => ,A (type-of (caadr (let ((b '(spam und eggs))) ``(,A ,@',B ,,17)))) ;; => SB-IMPL::COMMA (cadadr (let ((b '(spam und eggs))) ``(,A ,@',B ,,17))) ;; => ,@'(SPAM UND EGGS) (type-of (cadadr (let ((b '(spam und eggs))) ``(,A ,@',B ,,17)))) ;; => SB-IMPL::COMMA
This is because SBCL has an optimized internal backquote facility, with comma expressions. For example, in SBCL, we could obtain all the "comma'd expressions" by:
;; SBCL (remove-if-not #'sb-int::comma-p (alexandria:flatten (let ((b '(spam und eggs))) ``(,A ,@',B ,,17)))) ;; => (,A ,@'(SPAM UND EGGS) ,17)
In CCL 1.12.1, we find double backquotes produce "equivalent" output to what we'd naively expect:
;; CCL repl (let ((b '(spam und eggs))) ``(,A ,@',B ,,17)) ;; => (LIST* A (APPEND '(SPAM UND EGGS) (LIST 17)))