Skip to content

LISP - Backquote and Comma

Modern Lisp: Backquote and Comma for Code Generation

Section titled “Modern Lisp: Backquote and Comma for Code Generation”

Backquote (also known as quasiquote) is a fundamental tool in Lisp for constructing complex data structures, especially for generating code within macros. It acts like a ‘template engine’ for Lisp’s own data structures. Paired with comma and comma-at, it provides an elegant and readable way to build lists and code that would otherwise require convoluted calls to list, cons, and append.

OperatorNamePurpose
(backquote)QuasiquoteCreates a template. It quotes the expression that follows, but allows for specific parts to be ‘unquoted’.
, (comma)UnquoteEscapes from the backquote. The expression following the comma is evaluated, and its result is inserted into the template.
,@ (comma-at)Splicing UnquoteEvaluates the following expression (which must result in a list) and splices its elements into the surrounding list, rather than inserting the list itself.

Let’s see a simple example of building a list. We want to create the list (the value is 42) where 42 comes from a variable.

(let ((my-number 42))
;; Using backquote and comma is clean and readable
(let ((templated-list `(the value is ,my-number)))
(format t "Using backquote: ~s~%" templated-list))
;; For comparison, the equivalent using `list` is more verbose
(let ((manual-list (list 'the 'value 'is my-number)))
(format t "Using list: ~s~%" manual-list)))
Using backquote: (THE VALUE IS 42)
Using list: (THE VALUE IS 42)
  • (the value is ,my-number): The backquote treats the, value, and is as literal symbols.
  • ,my-number: The comma tells Lisp to stop quoting, evaluate the variable my-number (which yields 42), and insert the result into the list.

Now, let’s see how ,@ differs from ,. We want to insert the elements of one list into another.

(let ((inner-list '(b c d)))
;; Using comma-at splices the elements
(format t "With ,@: ~s~%" `(a ,@inner-list e))
;; Using a regular comma inserts the list itself
(format t "With , : ~s~%" `(a ,inner-list e)))
With ,@: (A B C D E)
With , : (A (B C D) E)

The real power of backquote shines when writing macros. Macros are functions that transform code, and backquote is the standard tool for building the code to be generated. Let’s create a simple macro with-gensyms which is a common utility.

(defmacro with-gensyms ((&rest names) &body body)
"A common macro that binds variables to fresh, uninterned symbols (gensyms).
This helps avoid accidental variable capture in macros."
`(let ,(mapcar (lambda (name) `(,name (gensym))) names)
,@body))
;; --- Macro Usage Example ---
;; We can now use our macro to create a `let` block with unique symbols.
;; Let's inspect the code our macro generates.
(let ((macro-expansion (macroexpand-1 '(with-gensyms (x y) (list x y)))))
(format t "Macro Expansion:~%~s~%" macro-expansion))
;; When run, it will produce a list of two unique, temporary symbols.
(format t "~%Result of running the macro: ~s~%" (with-gensyms (x y) (list x y)))
Macro Expansion:
(LET ((X (GENSYM)) (Y (GENSYM))) (LIST X Y))
Result of running the macro: (#:G1023 #:G1024)
;; Note: Your gensym names will be different.
  • `(let ...)`: The outer backquote starts our code template. We are generating a let expression.
  • ,(mapcar ...): The comma evaluates the mapcar expression. This expression builds the variable binding list for the let. For (x y), it generates ((X (GENSYM)) (Y (GENSYM))).
  • ,@body: The comma-at splices the body of the macro call into the let block. If our macro was called with multiple forms, like (with-gensyms (x) (print x) (1+ x)), ,@ ensures they are all placed inside the let.