Skip to content

LISP - 反引号与逗号

现代Lisp:反引号和逗号用于代码生成

Section titled “现代Lisp:反引号和逗号用于代码生成”

反引号(Backquote,也称为准引用 Quasiquote)是 Lisp 中用于构建复杂数据结构,尤其是在宏(macros)中生成代码的基本工具。它就像是 Lisp 自身数据结构的“模板引擎”。与逗号(comma)和逗号-at(comma-at)配合使用,它提供了一种优雅且可读的方式来构建列表和代码,否则将需要复杂的 list、cons 和 append 调用。

操作符名称目的
(反引号)准引用创建模板。它会引用其后的表达式,但允许对特定部分进行“解除引用”(unquoted)。
, (逗号)解除引用跳出反引号的引用作用。逗号后面的表达式会被求值(evaluated),其结果会被插入到模板中。
,@ (逗号-at)拼接解除引用求值其后的表达式(该表达式必须返回一个列表),并将其元素拼接(splices)到周围的列表中,而不是插入列表本身。

让我们看一个构建列表的简单示例。我们希望创建一个列表 (the value is 42),其中 42 来自一个变量。

(let ((my-number 42))
;; 使用反引号和逗号简洁且可读
(let ((templated-list `(the value is ,my-number)))
(format t "Using backquote: ~s~%" templated-list))
;; 作为比较,使用 `list` 的等效写法更冗长
(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、value 和 is 视为字面符号(literal symbols)。
  • ,my-number:逗号告诉 Lisp 停止引用,求值变量 my-number(其结果为 42),并将结果插入到列表中。

示例:使用逗号-at (,@) 进行拼接

Section titled “示例:使用逗号-at (,@) 进行拼接”

现在,让我们看看 ,@ 与 , 有何不同。我们希望将一个列表的元素插入到另一个列表中。

(let ((inner-list '(b c d)))
;; 使用逗号-at 拼接元素
(format t "With ,@: ~s~%" `(a ,@inner-list e))
;; 使用普通逗号插入列表本身
(format t "With , : ~s~%" `(a ,inner-list e)))
With ,@: (A B C D E)
With , : (A (B C D) E)

反引号的真正强大之处在于编写宏(macros)时。宏是转换代码的函数,而反引号是构建要生成代码的标准工具。让我们创建一个简单的宏 with-gensyms,它是一个常用工具。

(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))
;; --- 宏使用示例 ---
;; 现在我们可以使用宏来创建一个带有唯一符号的 `let` 块。
;; 让我们检查宏生成的代码。
(let ((macro-expansion (macroexpand-1 '(with-gensyms (x y) (list x y)))))
(format t "Macro Expansion:~%~s~%" macro-expansion))
;; 运行时,它将生成一个包含两个唯一临时符号的列表。
(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)
;; 注意:你的生成符号名称会有所不同。
  • `(let ...)`:外部的反引号启动了我们的代码模板。我们正在生成一个 let 表达式。
  • ,(mapcar ...):逗号求值 mapcar 表达式。该表达式构建 let 的变量绑定列表。对于 (x y),它会生成 ((X (GENSYM)) (Y (GENSYM)))。
  • ,@body:逗号-at 将宏调用体拼接进 let 块。如果我们的宏被调用时带有多个形式,例如 (with-gensyms (x) (print x) (1+ x)),,@ 会确保它们都被放置在 let 内部。