\( \newcommand\D{\mathrm{d}} \newcommand\E{\mathrm{e}} \newcommand\I{\mathrm{i}} \newcommand\bigOh{\mathcal{O}} \newcommand{\cat}[1]{\mathbf{#1}} \newcommand\curl{\vec{\nabla}\times} \newcommand{\CC}{\mathbb{C}} \newcommand{\NN}{\mathbb{N}} \newcommand{\QQ}{\mathbb{Q}} \newcommand{\RR}{\mathbb{R}} \newcommand{\ZZ}{\mathbb{Z}} \)
UP | HOME

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 form basic which is not a list or general vector
  • `,form is the same as form for any form provided the representation of form 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 as form
  • `(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)))

5. References

  • Alan Bawden,
    "Quasiquotation in Lisp".
    PDF.
  • Common Lisp The Language, Ch21 §1.3.

Last Updated 2023-08-14 Mon 10:20.