通过以下 Common Lisp 最小示例代码(来自更复杂的代码库),我收到有关行
((fboundp (car ',arg)) ,arg)
的警告,我无法解释为什么或如何避免它们。
(defmacro cond-issue (&key (arg '(a b c)))
(let ((arg-var (gensym "arg-var-")))
`(let ((,arg-var (cond ((not (listp ',arg)) (list 'arg))
((fboundp (car ',arg)) ,arg)
(t ',arg))))
`(apply #'+ ,,arg-var))))
(cond-issue :arg (e f g))
CCL抱怨:
;Compiler warnings :
; In an anonymous lambda form: Undefined function E
; In an anonymous lambda form: Undeclared free variable F
; In an anonymous lambda form: Undeclared free variable G
SBCL 抱怨:
; in: cond-issue :arg
; (E F G)
;
; caught style-warning:
; undefined function: common-lisp-user::e
;
; caught warning:
; undefined variable: common-lisp-user::f
;
; caught warning:
; undefined variable: common-lisp-user::g
;
; compilation unit finished
; Undefined function:
; e
; Undefined variables:
; f g
; caught 2 WARNING conditions
; caught 1 STYLE-WARNING condition
CLISP 不会抱怨。
使用 SBCL,我尝试了两种方法:编译和评估代码,警告方面没有区别。
这将是第二个
cond
表单的(愚蠢的)示例用例。
(cond-issue :arg (car '(e f g)))
;; ⇒ (apply #'+ e)
解释是什么,为什么CCL和SBCL抱怨?
那么如何编写代码来满足CCL和SBCL呢?
SBCL 和 CCL 抱怨的原因是他们正在编译代码。 CLISP REPL 解释代码。如果您打开新的 SBCL REPL 并将
eval
置于解释模式,则可以调用 OP 宏而不会触发警告:
CL-USER> (setf *evaluator-mode* :interpret)
:INTERPRET
CL-USER> (defmacro cond-issue (&key (arg '(a b c)))
(let ((arg-var (gensym "arg-var-")))
`(let ((,arg-var (cond ((not (listp ',arg)) (list 'arg))
((fboundp (car ',arg)) ,arg)
(t ',arg))))
`(apply #'+ ,,arg-var))))
COND-ISSUE
CL-USER> (cond-issue :arg (e f g))
(APPLY #'+ (E F G))
将
eval
放回到编译模式后,会出现警告:
CL-USER> (setf *evaluator-mode* :compile)
:COMPILE
CL-USER> (cond-issue :arg (e f g))
; in: COND-ISSUE :ARG
; (E F G)
;
; caught STYLE-WARNING:
; undefined function: COMMON-LISP-USER::E
;
; caught WARNING:
; undefined variable: COMMON-LISP-USER::F
;
; caught WARNING:
; undefined variable: COMMON-LISP-USER::G
;
; compilation unit finished
; Undefined function:
; E
; Undefined variables:
; F G
; caught 2 WARNING conditions
; caught 1 STYLE-WARNING condition
(APPLY #'+ (E F G))
CL-USER> (macroexpand-1 '(cond-issue :arg (e f g)))
(LET ((#:|arg-var-252|
(COND ((NOT (LISTP '(E F G))) (LIST 'ARG))
((FBOUNDP (CAR '(E F G))) (E F G)) (T '(E F G)))))
`(APPLY #'+ ,#:|arg-var-252|))
看上面的宏展开可以看出,要编译的代码的一个分支包含一个带有未定义函数和两个未定义变量的函数调用:
((FBOUNDP (CAR '(E F G))) (E F G))
。编译器会抱怨,因为它无法编译此代码。我认为这是您真正想要的行为,即您可能不应该尝试规避编译器对未定义函数和变量的警告。
考虑一些应该编译的代码:
CL-USER> (cond-issue :arg (list 1 2))
(APPLY #'+ (1 2))
CL-USER> (macroexpand-1 '(cond-issue :arg (list 1 2)))
(LET ((#:|arg-var-258|
(COND ((NOT (LISTP '(LIST 1 2))) (LIST 'ARG))
((FBOUNDP (CAR '(LIST 1 2))) (LIST 1 2)) (T '(LIST 1 2)))))
`(APPLY #'+ ,#:|arg-var-258|))
这里的形式
(LIST 1 2)
已经取代了之前的(E F G)
,这对编译器来说没有问题。请注意,上面的结果形式是 (APPLY #'+ (1 2))
,这不是合法的 Lisp 代码。我认为其目的是生成 (APPLY #'+ '(1 2))
。这是 cond-issue
的稍微修改版本,它解决了这个问题:
(defmacro cond-issue-fixed (&key (arg '(a b c)))
(let ((arg-var (gensym "arg-var-")))
`(let ((,arg-var (cond ((not (listp ',arg)) (list 'arg))
((fboundp (car ',arg)) ,arg)
(t ',arg))))
`(apply #'+ ',,arg-var))))
再次,我认为您不应该尝试规避这些编译器警告,但作为一个实验,并且本着修补半成品解决方案的精神,您可以将
cond-issue
包装在首先检查参数的宏中:
(defmacro cond-issue-check (&key (arg '(a b c)))
`(when (every (lambda (item)
(or (boundp item)
(fboundp item)))
(remove-if-not #'symbolp ',arg))
(cond-issue-fixed :arg ,arg)))
此解决方案有一个警告:
boundp
和fboundp
检查全局环境中的绑定,但它们无法检查本地绑定(我不确定是否有任何简单的方法可以做到这一点)。
CL-USER> (cond-issue-check :arg (e f g))
NIL
CL-USER> (cond-issue-check :arg (list 1 2))
(APPLY #'+ '(1 2))
CL-USER> (let ((a 1) (b 2))
(cond-issue-check :arg (list a b)))
NIL
在上面的最后一个示例中,检查包装器无法确定
a
和 b
的本地绑定是否已绑定。当需要本地绑定时,可以调用原始的未包装的 cond-issue
,但这意味着可能会触发未绑定函数或变量的警告。
在任何情况下,上述修复都不会停止所有缺少定义的警告。一般来说,我不建议尝试抑制这些类型的警告,也不建议使用
cond-issue-check
。最好让编译器完成它的工作。