我怎么知道我是否在打anaphoric macro?如果我不知道这样做的话,一些看似未绑定的符号的行为可能与预期的完全不同。
从列表中收集所有偶数很容易:
> (loop for i in '(1 2 3 4) ;correct
when (evenp i) collect i)
(2 4)
但是,如果有人对迭代变量命名为it
(因为“ it”似乎是“ item”的缩写)是一个好主意;那么C ++伙计们通常会使用称为{{ 1}}),结果突然变得完全不同:
it
这听起来可能是人为的,但是最近,一个令人尴尬的错误发生在我认识的人身上。
那么如何避免再次陷入同样的陷阱这种类型的错误?
答案 0 :(得分:7)
这是一个主要原因,即照应宏并没有得到所有人的普遍喜欢,人们通常会尽量少用它们。像if-let
这样的显式绑定在实践中似乎更被接受。
但是,据我所知,LOOP宏是规范中唯一提供隐式绑定的构造,但如果您认为相同的话,则可能是NIL块。此外,它已被广泛记录,并且不会很快改变。因此,给出的示例有点虚假。同时,不可否认这种错误可能发生。
那么如何避免此类错误?
也许您不需要执行任何操作。会发生错误,但是这一错误不太可能经常发生。
但是,如果您愿意,您可以决定限制语言以禁止在LOOP中使用it
(因为您担心自己或其他人会引入相同的错误):
(defpackage mycl (:use :cl) (:shadows #:loop))
(in-package mycl)
上面定义了CL的自定义方言,它遮盖了loop
符号。
软件包MYCL中的{em>可访问(在未给出软件包前缀时解析)的loop
符号是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))
您可以对发现有问题的其他构造应用相同的方法,但是请注意,通常,照应宏是由外部系统提供的,这是有意为之,因此不要感到意外。但是,即使您不知道某些宏是隐喻的,它们的文档甚至命名约定也应足以防止出错( anaphora 系统引入以a
开头的符号,例如aif
,awhen
或s
,例如scase
)。
如果您在交互式环境中(例如Emacs / Slime,但也有其他环境)工作,则显示附件或功能文档很容易。
答案 1 :(得分:6)
这就是为什么我不太喜欢照应宏的原因之一。
LOOP
宏会使情况稍差一些,因为标识符不与包一起用作符号-仅按名称使用。示例:
一个用户包中可能没有直接访问符号cl::it
的用户包:
(cl:loop for it in '(1 2 3 4)
when (cl:evenp it) collect it) ; this is still problematic
因此,局部符号it
仍将受到影响,因为回指变量it
仍在迭代变量it
的后面。因此,对符号it
使用您自己的软件包无济于事。
我没有答案,用户可以做什么-除了仔细阅读文档之外,肯定会突出显示(?!?)这个隐喻变量:
在循环中使用名为IT(在任何程序包中)的变量时要格外小心,因为它是一个循环关键字,在某些情况下可以代替表单。
宏的开发人员可能想检查用户是否以隐喻变量的名称(以相同的宏形式)定义了一个变量,并发出警告。仍然可以在宏的外部定义变量,这仍然可能成为问题的根源。
功能
函数可能会发生类似的事情:
(defmethod bar (a) (print (list :foo a)))
(defmethod bar :around (a)
(flet ((call-next-method ()
(print a)))
(call-next-method)))
在这里,我们需要知道DEFMETHOD
使本地函数CALL-NEXT-METHOD
可用。如果我们不小心用相同的名称定义了一个本地函数,那么我们将调用我们的版本-而不使用CLOS版本...