避免在不知不觉中使用照应宏的陷阱

Dom*_*riš 6 macros scope naming-conventions common-lisp

我怎么知道我是否称为回指?如果我在不知情的情况下这样做,一些看似未绑定的符号可能会与人们期望的完全不同.

从列表中收集所有偶数很容易:

> (loop for i in '(1 2 3 4)        ;correct
       when (evenp i) collect i)
(2 4)
Run Code Online (Sandbox Code Playgroud)

但是,如果有人得到了给迭代变量赋予名称的好主意it(因为"它"似乎是"item"的一个很好的缩写;也是C++人经常使用一个名为迭代器的迭代it),结果突然大不相同:

> (loop for it in '(1 2 3 4)         ;wrong
       when (evenp it) collect it)
(T T)
Run Code Online (Sandbox Code Playgroud)

这可能听起来很人为,但最近这个尴尬的错误发生在我认识的人身上.

那么如何避免再次陷入这种类型的错误呢?

cor*_*ump 7

这是一个主要的原因,因为人们通常会谨慎地使用它们,而且人们通常不会一致地使用它们.if-let在实践中似乎更接受明确的绑定.

然而,LOOP宏是规范中唯一的构造,据我所知,它提供了隐式绑定,如果你认为它是相同的,可能是NIL块的例外.此外,它有相当广泛的文件记录,不会很快改变.因此给出的例子感觉有点人为.与此同时,无可否认,这种错误可能会发生.

那么如何避免这种类型的错误呢?

也许你不需要做任何事情.错误发生,但这种情况不太可能经常发生.

但是如果你想,你可以决定限制语言禁止it在LOOP中使用(因为你担心你或其他人会引入同样的错误):

(defpackage mycl (:use :cl) (:shadows #:loop))
(in-package mycl)
Run Code Online (Sandbox Code Playgroud)

上面定义了CL的自定义方言,它隐藏了loop符号.从MYCL包中可访问loop符号(在没有给出包前缀时解析)是来自MYCL的符号,而不是CL:LOOP.然后,您可以添加自己的支票:

(defmacro loop (&body body)
  (when (find "IT" body :test #'string=)
    (error "Forbidden IT keyword"))
  `(cl:loop ,@body))
Run Code Online (Sandbox Code Playgroud)

这个定义应该足够了(可能会遗漏一些案例).然后,您选择在项目中使用此包而不是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.
Run Code Online (Sandbox Code Playgroud)

检查的另一种方法可以如下(通过尝试查看以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))
Run Code Online (Sandbox Code Playgroud)

您可以将相同的方法应用于您发现有问题的其他构造,但请注意,通常根据外部系统引入回指宏,这是有目的的,因此不应该让人感到意外.但是,即使你不知道你的一些宏都照应,他们的文档,甚至他们的命名规则应该足以防止错误(该照应系统介绍与开始符号a,像aif,awhensscase).如果您在交互式环境中工作(例如Emacs/Slime,还有其他环境),则可以轻松地显示附加到函数或宏的文档.

  • 感谢你的建议。事实上,从一位经验丰富的用户那里听到他们不知道任何进一步的此类示例正是我所期待的答案。因为我不太可能很快忘记“它”,所以我可能会选择选项 1(即,不做任何特别的事情)。但如果您发现任何进一步的示例,请告诉我。 (2认同)
  • 问题不在于照应宏,因为当你使用它时,你是故意使用它来利用它的“it”符号。问题是“loop”**不是**一个照应宏,它的“it”是一个模糊的功能,并且不需要对涉及“it”的令人惊讶的阴影情况进行诊断。 (2认同)

Rai*_*wig 6

这就是为什么我不太喜欢照应宏的原因.

LOOP宏使得略差,因为标识不作为与包符号-但名存实亡.例:

一个可能在用户包中没有直接访问该符号cl::it:

(cl:loop for it in '(1 2 3 4)
         when (cl:evenp it) collect it)  ; this is still problematic
Run Code Online (Sandbox Code Playgroud)

因此,局部符号it仍然会受到影响,因为回指变量仍然会影响it迭代变量it.因此,对符号使用自己的包没有帮助it.

我没有答案用户可以做什么 - 除了仔细阅读文档,其中的回指变量肯定会被突出提及(?!?):

CLHS 6.1.9关于循环的注释

在使用与循环相关的名为IT(在任何包中)的变量时要小心,因为它是一个循环关键字,可以在某些上下文中用来代替表单.

宏的开发人员可能想要检查用户是否使用相应宏形式的回指变量的名称定义变量,并发出警告.变量也可能在宏之外定义 - 这仍然可能是问题的根源.

功能

功能可能会发生类似的事情:

(defmethod bar (a) (print (list :foo a)))

(defmethod bar :around (a)
  (flet ((call-next-method ()
           (print a)))
    (call-next-method)))
Run Code Online (Sandbox Code Playgroud)

在这里,我们需要知道DEFMETHOD使本地函数CALL-NEXT-METHOD可用.如果我们不小心定义了具有相同名称的本地函数,那么我们将调用我们的版本 - 而不是使用CLOS版本...