为何我的环境中Common Lisp宏编译结果与书中不同?能否复现?
问题原因
你遇到的差异本质是Common Lisp实现对宏展开时机的选择差异,不是操作错误:
- Common Lisp标准没有强制要求宏展开的具体时机,只要求宏在其生成的代码执行前完成展开即可。不同实现、甚至同一实现的不同执行模式(解释执行/编译执行、REPL输入/文件编译)的展开时机都可能不同。
- 你使用的SBCL在REPL交互模式下,默认会在执行
defun定义函数的时候就完成内部宏调用的展开,所以你在定义say-hi的时候已经输出了Hi mom!,后续调用compile 'say-hi时,函数体内已经没有未展开的宏,自然不会再次触发宏的执行,也就不会重复输出内容。 - 书中示例所用的老式Lisp实现,默认在
defun定义函数时不会做宏展开,只有在显式调用compile编译函数、或者首次执行函数的时候才会展开宏,所以才会在编译步骤输出Hi mom!。
复现书中输出的方法
你可以通过文件编译的方式复现书中的效果,步骤如下:
- 重启SBCL清空所有已有的定义,避免之前的展开结果干扰
- 将如下代码写入
test.lisp文件,不要在REPL中提前执行:
(defmacro bad-announce-macro () (format t "~%Hi mom!")) (defun say-hi () (bad-announce-macro))
- 在REPL中执行
(compile-file "test.lisp"),此时会在编译阶段触发宏展开,你就能看到编译输出里出现Hi mom!,和书中示例效果一致。
补充你执行(compile (say-hi))报错的原因:say-hi函数返回值是NIL,你把NIL传给compile函数当然会触发未定义函数错误,正确用法就是你后面写的(compile 'say-hi),传入函数名的符号即可。
核心知识点提醒:这个示例的本质目的就是提醒开发者不要把副作用写在宏本身的代码里,副作用应该放在宏生成的返回表达式中。如果要实现「调用时输出Hi mom」的正确效果,代码应该写成:
(defmacro good-announce-macro () '(format t "~%Hi mom!"))此时宏本身没有副作用,只是返回一个带副作用的表达式,只有在运行期调用宏生成的代码时才会输出内容,无论展开时机如何行为都一致。
内容的提问来源于stack exchange,提问作者Pedro Delfino
相关产品推荐
相关产品推荐

