SBCL中CCASE与CASE、ECASE的异常行为问题
Ecase与Ccase在SBCL 2.0.1中的行为差异解析
在SBCL 2.0.1版本中,使用ecase处理符号'dos时运行正常,但用ccase处理同一个符号会触发编译警告,查阅CLTL2规范未找到直接解释,核心原因在于两个宏的设计目标和参数要求存在本质区别。
代码示例
Ecase 运行结果
(ecase 'dos ((i uno) 1) ((ii dos) 2) ((iii tres) 3)) ; 输出:2
Ccase 运行警告与结果
(ccase 'dos ((i uno) 1) ((ii dos) 2) ((iii tres) 3)) ; 编译警告信息: ; in: CCASE 'DOS ; (SETF 'DOS ; (SB-KERNEL:CASE-BODY-ERROR 'CCASE ''DOS #:G680 ; '(MEMBER I UNO II DOS III TRES) ; '(I UNO II DOS III TRES))) ; --> LET* FUNCALL ; ==> ; (SB-C::%FUNCALL #'(SETF QUOTE) #:NEW1 #:DOS3) ; ; caught WARNING: ; The function (SETF QUOTE) is undefined, and its name is reserved by ANSI CL so ; that even if it were defined later, the code doing so would not be portable. ; ==> ; (LET* ((#:DOS3 DOS) ; (#:NEW1 ; (SB-KERNEL:CASE-BODY-ERROR 'CCASE ''DOS #:G680 ; '(MEMBER I UNO II DOS III TRES) ; '(I UNO II DOS III TRES)))) ; (FUNCALL #'(SETF QUOTE) #:NEW1 #:DOS3)) ; ; caught WARNING: ; undefined variable: COMMON-LISP-USER::DOS ; ; compilation unit finished ; Undefined function: ; (SETF QUOTE) ; Undefined variable: ; DOS ; caught 2 WARNING conditions ; 输出:2
差异原因解析
宏的核心设计区别
ecase的第一个参数是普通表达式:它只需要计算表达式的值,然后匹配分支,不需要对输入做任何修改。所以'dos作为常量表达式完全合法,直接取值匹配即可。ccase的第一个参数是可修改位置(place):这个宏的设计目的是,当匹配失败时,允许通过用户提供的错误处理函数修改该位置的值,然后重新尝试匹配。因此它要求第一个参数必须是能被setf修改的合法位置,比如变量、数组元素、对象槽位等。
警告的具体由来
- 当给
ccase传入'dos时,SBCL会尝试生成代码去修改这个"位置",但'dos是quote的结果,属于常量,根本不能被赋值。编译器把这个操作解析为(setf 'dos ...),这相当于试图调用(setf quote)函数——而ANSI Common Lisp明确保留了(setf quote)的名字,不允许定义该函数,因此触发第一个警告。 - 第二个警告是因为编译器错误地将
'dos展开为对变量DOS的引用,但这个变量从未被定义过,所以提示未定义变量。
- 当给
正确使用场景
- 如果只需要根据值匹配分支,不需要修改输入,用
ecase即可。 - 如果需要在匹配失败时修改输入位置的值(比如让用户输入新值后重试),才使用
ccase,此时第一个参数必须是合法的可赋值位置,比如:
这段代码就不会触发警告,因为(let ((x 'dos)) (ccase x ((i uno) 1) ((ii dos) 2) ((iii tres) 3)))x是可修改的变量。
- 如果只需要根据值匹配分支,不需要修改输入,用
内容的提问来源于stack exchange,提问作者John Badie
相关产品推荐
相关产品推荐

