这是照应宏未得到一致喜欢的主要原因,人们通常会尽量谨慎地使用它们。像if-let 这样的显式绑定在实践中似乎更受欢迎。
然而,据我所知,LOOP 宏是规范中唯一提供隐式绑定的构造,但 NIL 块可能除外,如果您认为它是相同的。此外,它已被广泛记录,并且不会很快改变。因此,给出的示例感觉有点人为。同时,不可否认,这种bug可能会发生。
那么如何避免此类错误呢?
也许你不需要做任何事情。错误会发生,但这种错误不太可能经常发生。
但如果您愿意,您可以决定限制语言以禁止在 LOOP 中使用it(因为您担心您或其他人会引入相同的错误):
(defpackage mycl (:use :cl) (:shadows #:loop))
(in-package mycl)
上面定义了一个自定义的 CL 方言,它隐藏了 loop 符号。
loop 符号是可访问(在没有给出包前缀时解决)来自包 MYCL 的符号来自 MYCL,而不是 CL:LOOP。
然后,您可以添加自己的检查:
(defmacro loop (&body body)
(when (find "IT" body :test #'string=)
(error "Forbidden IT keyword"))
`(cl:loop ,@body))
这个定义应该足够了(它可能会遗漏某些情况)。
然后,你选择在你的项目中使用这个包而不是 CL,因此,以下失败并出现错误:
(defun test ()
(loop
for it in '(1 2 3 4)
when (evenp it) collect it))
...
error:
during macroexpansion of
(LOOP
FOR
IT
...).
Use *BREAK-ON-SIGNALS* to intercept.
Forbidden IT keyword
Compilation failed.
另一种检查方法如下(通过尝试查看所有以 LOOP 为根的树更严格,因此即使对于其他有效情况也可能出错):
(defmacro loop (&body body)
(unless (tree-equal body (subst nil
"IT"
body
:test #'string=
:key (lambda (u)
(typecase u
((or symbol string) (string u))
(t "_")))))
(error "Forbidden IT keyword"))
`(cl:loop ,@body))
您可以将相同的方法应用于您发现有问题的其他构造,但请注意,通常照应宏是由外部系统带来的,这是故意完成的,因此不应让人感到意外。但是即使你不知道你的一些宏是照应,它们的文档甚至它们的命名约定也应该足以防止错误(照应系统引入了以a开头的符号,比如@ 987654330@、awhen 或 s,如 scase)。
如果您在交互式环境(例如 Emacs/Slime,但也包括其他环境)中工作,则可以轻松地显示附加到函数或宏的文档。