问题描述
这个问题与之前关于 programmatically generating symbol macros 的一个问题有些相关。我在一个方便的宏中使用该函数,该宏会抛出未定义的变量警告。这个宏和函数:
(defmacro define-data (d body &optional doc)
(if (and doc (not (stringp doc))) (error "Documentation is not a string"))
`(let* ((d-str (string ',d))
(old-package *package*)
(*package* (if (find-package d-str) ;exists?
(find-package d-str) ;yes,return it
(make-package d-str)))) ;no,make it
;; Should we have an eval-when (:compile-toplevel) here?
(defparameter,d,body,doc)
(export ',d old-package)
(define-column-names,d)))
(defun define-column-names (d)
(maphash #'(lambda (key index)
(eval `(cl:define-symbol-macro,key (cl:aref (columns,d),index))))
(ordered-keys-table (slot-value d 'ordered-keys))))
旨在类似于 defparameter
,但另外通过定义为用户设置了一些细节:
- 一个名为
d
的包 - 当前包中的参数,其中包含将被
body
吸入的数据 - 包
d
中的符号宏,用于访问各个数据向量
如果我使用 REPL 中的 defparameter
,然后调用 define-column-names
,一切都很好。但是,当使用宏时,我得到:
; in: DEFINE-COLUMN-NAMES FOO
; (DEFINE-COLUMN-NAMES CL-USER::FOO)
;
; caught WARNING:
; undefined variable: CL-USER::FOO
我怀疑这是因为编译器无法知道在调用 define-symbol-macro
时实际上会定义 FOO。一切正常,但我不希望警告吓到用户,所以我想压制它。不过我讨厌压制警告,所以我想我会来这里征求第二意见。
编辑:我已将答案标记为正确,因为它确实正确回答了所问的问题。有关问题的答案,请参阅我的评论。
解决方法
我对标题中“何时消除警告”问题的回答是:如果是你自己的代码,那么在任何情况下都不要。如果是别人的代码,那就重写它不要警告,除非你不能。
至于解决这个问题我还没有认真考虑过,但问题是你肯定希望 defparameter
处于顶层以便编译器可以看到它,而它不能如果它在 let
内,确实如此。但是您可以轻松地将其提升到顶级,因为它不依赖于 let
内的任何内容。
我非常确定您希望宏的其余部分在编译时发生,因为您肯定希望符号宏在编译时可用。所以对第一个宏的尝试是(注意我已经修复了文档字符串的处理:(defparameter foo 1 nil)
不好):
(defmacro define-data (d body &optional doc)
(when (and doc (not (stringp doc)))
(error "Documentation is not a string"))
`(progn
(defparameter,d,body,@(if doc (list doc) '()))
(eval-when (:compile-toplevel :load-toplevel :execute)
(let* ((d-str (string ',d))
(old-package *package*)
(*package* (if (find-package d-str) ;exists?
(find-package d-str) ;yes,return it
(make-package d-str)))) ;no,make it
(export ',d old-package)
(define-column-names,d)))))
附带说明:虽然我认为以编程方式定义符号宏很困难,因为 CL 出于某种原因将其排除在外,但我认为我个人会使用其他方法而不是这种方法,因为 eval
是太可怕了。然而,这只是我:如果你想这样做,你确实需要 eval
我认为(这是非常罕见的!)。
我不确定 define-columns-names
究竟是如何工作的,所以我用一个返回 d
的存根函数替换了它。
另请注意,您可以使用 check-type
并且应该尽量不要在生成的代码中注入符号,这会引入可以使用 gensym
避免的潜在变量捕获。
据我所知,您不能按照您的评论建议使用 eval-when
(有关详细信息,请参阅 Issue EVAL-WHEN-NON-TOP-LEVEL Writeup)。
但是如果我在调用中声明符号是特殊的,我不会发出警告。
(defmacro define-data (d body &optional doc)
(check-type doc (or null string))
(check-type d symbol)
(let ((d-str (string d)))
(alexandria:with-gensyms (old-package)
`(let* ((,old-package *package*)
(*package* (if (find-package,d-str) ;exists?
(find-package,d-str) ;yes,return it
(make-package,d-str)))) ;no,make it
(defparameter,doc)
(export ',old-package)
(locally (declare (special,d))
(define-column-names,d))))))
将调用扩展为对 define-column-names
的调用也有点奇怪,它反过来评估在运行时构建的表单。我认为在宏观扩展期间可以做所有你想做的事情,但正如前面所说,你想要做什么对我来说有点不清楚。我的想法是将 define-column-names
替换为:
,@(expand-column-names-macros d)
... 其中 expand-column-names-macros
构建 define-symbol-macro
表单列表。