Programming Languages · Metaprogramming
Quasiquotation, Unquotation, and Nesting Depth
How quasiquote builds template structures with holes, why unquote must match the right quasiquote level, and how the level counter decides which parts of a nested template are evaluated.
A macro must return a structure in which some positions are literal and others are filled from the
invocation. Writing that structure as an ordinary constructor call is possible and unreadable:
building a three-level nested list out of cons calls obscures the shape being built. Quasiquotation
is the notation that makes the shape visible again, and its only real subtlety is that a template may
contain another template, at which point the notation needs to say which holes belong to which level.
The four markers and what each one does
The notation is a small family of reader prefixes. quote suppresses evaluation entirely and yields
the datum as written. quasiquote suppresses evaluation except where a hole is marked. unquote
marks a hole at the current level. unquote-splicing marks a hole whose value is a list that should
be spliced into the enclosing list rather than inserted as a single element.
`(a b ,x) ; a three-element list: a, b, and the value of x
`(a b ,@xs) ; a list starting a, b, then the elements of xs
`(a . ,x) ; a dotted pair: a in the car, value of x in the cdr
The distinction between ,x and ,@xs is structural, not cosmetic. The first inserts one element;
the second inserts however many elements the list has, so the resulting list’s length depends on a
value that is not known when the template is written. Splicing is only defined in a list position —
splicing into the tail of a dotted pair is an error in most implementations, because there is no
enclosing list for the elements to be part of.
The reader’s role is worth separating from the expander’s. ` is read as the two-element form
(quasiquote ...), , as (unquote ...), and ,@ as (unquote-splicing ...). A macro expander
that receives a template is therefore receiving ordinary nested lists whose heads are these symbols,
and the expansion of a quasiquote is a recursive walk over that structure. Nothing about the notation
is primitive; it is a surface syntax for a particular shape of tree, and implementations are free to
expand it by interpretation or by compiling it into constructor calls.
Depth: the counter that decides
The nesting rule is a single integer. Entering a quasiquote increments the level; leaving it
decrements. An unquote at level n is meaningful only if it appears inside exactly n nested
quasiquotes, and it cancels the innermost one. An unquote seen at a level other than one is either
an error or, in some implementations, a datum to be preserved for a later expansion.
`(a `(b ,(c ,x)))
Reading outward: the first quasiquote sets level 1, the second sets level 2. The comma before
(c ,x) sits at level 2, so it cancels the inner quasiquote and makes its argument a level-1
expression — part of the outer template. That argument is itself (c ,x), whose comma is at level 1,
so x is evaluated immediately, while the outer template is being built. What remains is the form
(c <value>), still wrapped in an unquote inside the inner quasiquote. With x bound to 1 the whole
expression yields (a (quasiquote (b (unquote (c 1))))): substituting x happened at the outer
level, and applying c is deferred until the inner template is evaluated.
This is the mechanism that makes macros that write macros possible. The outer macro needs to produce a definition whose body is itself a template, and the holes belonging to the outer macro must be filled at the outer level while the holes belonging to the inner macro must survive as holes.
Some Lisp dialects make the level explicit at the hole rather than inferring it from the surrounding
nesting, writing a doubled comma for a hole that belongs to the outer template. The suffix removes
the counting exercise from the reader but introduces a second way to be wrong: a suffix that does not
match the surrounding depth. Common Lisp is a partial case at the specification level. The standard
reserves the notation for interpretation without fully specifying how nested backquote is read, so an
implementation that accepts ,, may treat it differently from another, and code that relies on the
nested behaviour is not portable. Scheme’s syntax-rules takes a third position: its templates have
no explicit quasiquote at all, and pattern variables are substituted wherever they appear, with
... marking repetition. The depth problem does not arise there because there is only ever one
level.
Building the expander
A quasiquote expander is a recursive function over the template with a level parameter. At a list
node, it distinguishes three cases: the head is unquote and the level is 1, in which case the
argument is emitted as an expression; the head is unquote and the level is greater than 1, in which
case the node is rebuilt with the level decremented; and the head is quasiquote, in which
case the level is incremented. Everything else is rebuilt element by element.
(define (qq-expand tmpl level)
(cond
((not (pair? tmpl))
(list 'quote tmpl)) ; an atom is embedded as a literal
((eq? (car tmpl) 'unquote)
(cond
((= level 1) (cadr tmpl)) ; a hole: emit it as an expression
(else (list 'cons (list 'quote 'unquote) ; rebuild it one level shallower
(qq-expand (cdr tmpl) (- level 1))))))
((eq? (car tmpl) 'quasiquote)
(list 'cons (list 'quote 'quasiquote)
(qq-expand (cdr tmpl) (+ level 1))))
(else (list 'cons ; cons the two halves
(qq-expand (car tmpl) level)
(qq-expand (cdr tmpl) level)))))
cond has no => clauses here, so an if is not a clause: the unquote branch must itself be a
cond whose two clauses are the hole and the rebuild. Written as a single three-part if inside the
outer cond, the expression is a syntax error rather than a wrong answer.
The output of this walk is a program that constructs the template: a nest of cons calls with the
hole expressions left in place to be evaluated by the surrounding let or lambda. Compiling the
template rather than interpreting it is the standard implementation, and it matters because the
constructed program is then subject to ordinary constant folding — a template with no holes at all
collapses to a constant, and the compiler can allocate it once instead of rebuilding it at every
invocation.
The splicing case needs one more piece. A spliced hole cannot be compiled as a plain cons, because
its length is unknown; the expander emits an append between the segments before and after the hole,
and a variadic constructor such as list for the segments that are known to be flat. A template with
k splices compiles to k+1 segment constructions joined by k append calls, and the append is
where the cost lives: it copies the spine of each following segment. A template that splices a long
list in front of a long literal tail therefore copies that tail at every expansion.
Implementations that care emit a list* chain instead, but the asymptotic shape is the same.
Where the notation leaks
Three failure modes recur.
An unquote at the wrong level is the first. Because the level is implicit in Scheme, a template that is one quasiquote shallower than the author believed produces an unquoted hole where a literal was intended, and the resulting form is evaluated rather than quoted. The error surfaces as an unbound variable or as a call to a function that does not exist, at a location far from the template.
The second is a splice in a non-list position. `(a . ,@xs) has no sensible meaning, and
implementations disagree on whether it is a read-time error, an expansion-time error, or a
construction of something malformed.
The third is the interaction with hygiene. A template is the primary source of identifiers that the macro introduces, so the expander must record, for each literal identifier in a template, the context of the macro definition. When a template is itself constructed by another macro, the constructed template’s identifiers carry the context of whichever expansion produced them, which is not always the context the eventual user expects. The rules for this are the hardest part of a hygienic expander, and they are why the syntax-object representation described in macro hygiene and identifier capture stores a scope set on every identifier rather than a single origin.
Templates in systems without the notation
Not every macro facility exposes quasiquotation, and the substitutes show what the notation is
actually for. C’s replacement list is a template with # and ## as its hole markers, and it has no
nesting at all — a macro cannot generate a macro definition, because the preprocessor has no way to
express “this directive is produced rather than obeyed”. Rust’s macro_rules! is closer: $name
substitutes a fragment, $( ... )* repeats a group over a matched sequence, and the whole thing is
one level, so nested templates require a second macro to carry the inner one.
macro_rules! count {
() => { 0usize };
($head:tt $($tail:tt)*) => { 1usize + count!($($tail)*) };
}
The repetition operator is quasiquotation’s splicing counterpart: $( ... )* iterates over the
variables bound by the pattern’s repetition, and the separator between repetitions is written after
the *. There is no level counter because the replacement is not evaluated — it is a token sequence
handed to the parser — so the depth problem that motivates ,, in Common Lisp simply does not
exist. What replaces it is the problem of a fragment that must be re-parsed after substitution,
covered in
procedural macros over token streams.
The general lesson is that quasiquotation is a notation for a specific operation: constructing a tree with designated holes, where the holes may be single values or sequences. Its level counter exists because the operation is closed under itself — a template can contain a template — and any system that supports generating code which generates code needs an equivalent way to say which holes belong to which generation.
References
- Revised^7 Report on the Algorithmic Language Scheme — Sperber, Findler, Flatt et al.; Journal of Functional Programming 19(S1), 2009
- Common Lisp the Language, 2nd edition — Guy L. Steele Jr., Digital Press, 1990
- Common Lisp HyperSpec, section 2.4.6.1 — Notes about Backquote — LispWorks
- The Rust Reference — Macros by Example — The Rust project
- Lisp 1.5 Programmer's Manual — McCarthy et al., MIT Press, 1962