LISP - 反引号与逗号
现代Lisp:反引号和逗号用于代码生成
Section titled “现代Lisp:反引号和逗号用于代码生成”反引号(Backquote,也称为准引用 Quasiquote)是 Lisp 中用于构建复杂数据结构,尤其是在宏(macros)中生成代码的基本工具。它就像是 Lisp 自身数据结构的“模板引擎”。与逗号(comma)和逗号-at(comma-at)配合使用,它提供了一种优雅且可读的方式来构建列表和代码,否则将需要复杂的 list、cons 和 append 调用。
核心三件套:``,,和,@`
Section titled “核心三件套:``,,和,@`”| 操作符 | 名称 | 目的 |
|---|---|---|
(反引号) | 准引用 | 创建模板。它会引用其后的表达式,但允许对特定部分进行“解除引用”(unquoted)。 |
| , (逗号) | 解除引用 | 跳出反引号的引用作用。逗号后面的表达式会被求值(evaluated),其结果会被插入到模板中。 |
| ,@ (逗号-at) | 拼接解除引用 | 求值其后的表达式(该表达式必须返回一个列表),并将其元素拼接(splices)到周围的列表中,而不是插入列表本身。 |
示例:基本模板
Section titled “示例:基本模板”让我们看一个构建列表的简单示例。我们希望创建一个列表 (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)主要用例:编写宏
Section titled “主要用例:编写宏”反引号的真正强大之处在于编写宏(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内部。