避免不经意间使用照应宏的陷阱

时间:2018-07-31 19:47:44

标签: scope macros common-lisp naming-conventions

我怎么知道我是否在打anaphoric macro?如果我不知道这样做的话,一些看似未绑定的符号的行为可能与预期的完全不同。

示例

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

> (loop for i in '(1 2 3 4)        ;correct
       when (evenp i) collect i)
(2 4)

但是,如果有人对迭代变量命名为it(因为“ it”似乎是“ item”的缩写)是一个好主意;那么C ++伙计们通常会使用称为{{ 1}}),结果突然变得完全不同:

it

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

那么如何避免再次陷入同样的​​陷阱这种类型的错误?

2 个答案:

答案 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开头的符号,例如aifawhens,例如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使用您自己的软件包无济于事。

我没有答案,用户可以做什么-除了仔细阅读文档之外,肯定会突出显示?!?)这个隐喻变量:

CLHS 6.1.9 Notes about Loop

  

在循环中使用名为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版本...