LISP - 宏
Lisp - 宏简介
Section titled “Lisp - 宏简介”宏是 Common Lisp 最强大和最具代表性的特性之一。它们允许你扩展语言自身的语法。俗话说,宏是生成代码的代码。
与在运行时操作值的函数不同,宏是一个转换函数,它接收 Lisp 代码(S-表达式)作为输入,并在编译时生成新的 Lisp 代码作为输出。然后,这段新代码将被编译并执行,以替代原始的宏调用。
使用 defmacro 定义宏
Section titled “使用 defmacro 定义宏”你可以使用 defmacro 特殊操作符来定义宏。其语法与 defun 类似:
(defmacro macro-name (parameter-list) "Optional documentation string." body-of-macro)宏的主体必须返回一个 Lisp 形式(一段代码),该形式将替换宏调用。
示例 1:一个简单的日志宏
Section titled “示例 1:一个简单的日志宏”让我们创建一个 with-logging 宏,它在执行代码块前后打印一条消息。这是函数无法实现的功能,而且无需改变代码的调用方式。
要在宏内部构建代码,我们使用反引号 ( ) 语法。它就像一个模板。我们使用逗号 (,) 对值进行求值并将其插入到这个模板中,使用逗号-at (,@) 来拼接一个值列表。
main.lisp
Section titled “main.lisp”(defmacro with-logging ((&key (stream '*standard-output*)) &body body) "Executes BODY, printing a log message before and after." `(progn (format ,stream "--- Entering logged section ---") ,@body (format ,stream "--- Exiting logged section ---")))
;; 现在,让我们使用新语法(with-logging () (print "Doing some work...") (let ((x 10) (y 20)) (format t "The sum is ~a~%" (+ x y))))当你执行这段代码时,Lisp 会首先将宏展开为 progn 形式,然后运行生成的代码:
--- Entering logged section ---"Doing some work..."The sum is 30--- Exiting logged section ---参数列表中的 &body 关键字将所有后续形式收集到一个名为 body 的列表中。然后,宏主体构建一个 progn 块(该块按顺序求值表达式)。,@body 将 body 变量中的形式列表直接拼接(splice)到 progn 块中。
示例 2:创建 while 循环
Section titled “示例 2:创建 while 循环”Common Lisp 有 loop,但让我们定义自己的 while 循环,以展示宏如何创建新的控制流结构。
main.lisp
Section titled “main.lisp”(defmacro while (test &body body) "Executes BODY as long as TEST evaluates to true." `(loop while ,test do (progn ,@body)))
;; 使用我们新的 `while` 循环(let ((counter 3)) (while (> counter 0) (format t "Counter is ~a~%" counter) (decf counter)))这段代码会展开成一个标准的 loop 宏调用,并产生预期结果:
Counter is 3Counter is 2Counter is 1- 创建新语法或控制流结构:如
with-logging和while所示。 - 控制求值:当你需要阻止参数被求值,或控制它们被求值的次数时(例如,短路逻辑运算符如
and和or)。 - 在编译时减少样板代码:编写生成重复但略有不同的定义的代码。
- 指导原则:如果你可以用普通函数解决问题,就使用函数。只有当函数不足以解决问题时,才考虑使用宏。