在#.读取宏中使用defconstant是否必须搭配eval-when?
CCL中defconstant配合#.读取宏的编译行为是否符合Common Lisp标准?
问题背景
在Common Lisp里,可通过#.读取宏获取符号化的case标签,示例代码如下:
(defconstant +foo+ 1) (defconstant +bar+ 2) (defun quux (x) (ecase x (#.+foo+ "Foo!") (#.+bar+ "Bar.")))
这段代码在REPL或直接加载文件时,各实现均能正常运行,但使用compile-file(或通过ASDF构建)时:
- SBCL可正常完成编译
- CCL会报错:
Error: Unbound variable: +FOO+
将defconstant用eval-when包裹后,SBCL与CCL均可正常编译。现需明确两个核心问题:
- CCL的上述行为是否符合Common Lisp标准?
- 可移植代码在此场景下是否必须使用
eval-when?
标准层面的严谨解答
以ANSI Common Lisp标准(而非CLTL2——它是标准出台前的参考文档,最终规范以ANSI CL为准)为依据,相关规则如下:
defconstant的定义会在编译阶段被记录到编译环境,但#.读取宏的执行时机是读取阶段——也就是代码进入编译流程前的解析环节。compile-file处理文件的完整流程是:读取→编译→加载。#.+foo+会在读取阶段就被求值,此时若+foo+未在环境中完成绑定,就会触发未绑定变量错误。
ANSI CL并未强制要求编译器在读取阶段就将defconstant的绑定暴露到环境中,仅要求在编译阶段(代码生成时)和加载阶段确保常量存在。不同实现对编译环境的处理策略存在差异:
- SBCL在处理
defconstant时,会在读取阶段就将常量定义注入当前环境,因此#.读取宏能正常找到+foo+的绑定。 - CCL严格区分读取阶段与编译阶段的环境,
defconstant的绑定要到编译阶段才生效,而#.在读取阶段就需要求值,因此找不到变量绑定——这种行为符合标准允许的范围,因为标准未规定实现必须在读取阶段暴露defconstant的绑定。
结论:可移植代码必须使用eval-when确保defconstant在读取阶段就被求值并绑定,正确写法如下:
(eval-when (:compile-toplevel :load-toplevel :execute) (defconstant +foo+ 1) (defconstant +bar+ 2)) (defun quux (x) (ecase x (#.+foo+ "Foo!") (#.+bar+ "Bar.")))
eval-when的三个关键字指定表达式会在编译顶层、加载顶层和执行阶段都被求值,这样在读取#.+foo+时,+foo+已完成绑定,所有实现都能正确处理。
CLTL2的描述存在歧义,最终需以ANSI Common Lisp标准的规定为准。
内容的提问来源于stack exchange,提问作者Chris
相关产品推荐
相关产品推荐

