【问题标题】:What can go wrong when I define a macro and just use a rare enough name for a temporary variable?当我定义一个宏并且只为一个临时变量使用一个足够罕见的名称时会出现什么问题?
【发布时间】:2013-06-30 11:31:44
【问题描述】:

Let Over Lambda Chapter 3 Section 'Unwanted Capture' 说:

“当然我们可以想出足够稀有的名字,这样问题就永远不会出现了。是的,在很多情况下,包和智能变量命名可以解决变量捕获的问题。但是,大多数严重的变量捕获错误不会出现在由程序员直接创建的代码中。大多数变量捕获问题仅在其他宏以您未预料到的方式使用您的宏(与您的宏结合)时才会出现。"

然后它没有给我一个粗体部分的例子。这样的例子之一是什么?想象一个假想的 Lisp 开发团队,其疯狂的老板禁止使用 gensym 或任何会创建 uninterned 符号的东西,而程序员只要在错过 gensym 时就简单地投掷字母数字骰子来想出诸如 temp-27s63f8sk2n 或 sum-3t84hj4df 之类的随机变量名称。什么是团队会遇到麻烦的例子?

说到这里,Emacs 24.3.1 定义了 dotimes 和 dolist ,没有使用 uninterned 符号。很奇怪。

【问题讨论】:

  • 为什么人们投票结束这个?这是一个真实而复杂的问题。

标签: macros lisp scope


【解决方案1】:

好的,那么我建议自动化“投掷字母数字骰子”的过程。当然,它不一定是随机的,你可以使用一个计数器。此外,如果能够指定前缀以进行调试,那就太好了。哦等等,这正是gensym 所做的。

【讨论】:

  • Gensym 返回非驻留符号。假设团队仅使用 INTERNED 符号。如果团队依赖intern-gensym,它类似于gensym,只是它返回了实习符号,那么他们就会遇到麻烦,因为名称 G10、G11、G12.... 还不够稀有,而且有机会宏的用户会使用这样的变量名,在链接的 Let Over Lambda 文章中给出了这样的例子。
  • 如果团队使用带有随机后缀的实习符号,则可以避免此类示例的问题。然后文章说还有另一种惹麻烦的方法,就是引文中的粗体部分。加粗的部分会不会给团队带来麻烦?
【解决方案2】:

当您在其他上下文中重新使用自己的宏时,问题就出现了,而您巧妙地命名空间变量实际上是多余的,因为它们都在同一个命名空间中。

我可以想到一个例子,当使用闭包访问封闭(let) 中的变量,但传递给一个宏,该宏也使用封闭(let) 定义具有名称冲突的“安全”变量。这是一个人为的例子,抱歉,我现在想不出一个真实的案例。

(defmacro my/a (x)
  (let ((my/safe-name x))
    `(progn ,(my/b (lambda () my/safe-name))
            ,my/safe-name)))

(defmacro my/b (f)
  `(let ((my/safe-name 4))
     (when (evenp (funcall ,f))
       (print "F is even!"))))

(my/a 3) ; will print "F is even", but it shouldn't

【讨论】:

  • 通常的开发团队会使用 两个 gensym 调用或 make-symbol 调用来编写它。假设的开发团队将运行他们的伪随机字母数字后缀生成器两次。所以它将是 my/a 中的 ((my/safe-name-6kuyd4kq x)) 和 my/b 中的 ((my/safe-name-pqedzkwg 4))。为了让这个团队陷入困境,我们需要将代码扩展为以某种方式获取 my/safe-name-6kuyd4kq 并使其以某种方式与自身发生冲突的代码。我怀疑这就是粗体部分的意思,只是它没有一个例子,所以我不能确定。
  • 使用 gensym 正是 Doug Hoyte 建议的解决不需要的捕获的方法。如果您忽略使用 gensyms,您只会遇到问题。这就是霍伊特想要表达的。在本书的这一点上,他假设你还不知道 gensyms,只知道手工挑选(但命名空间)的变量名。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-02-01
  • 2023-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-12
  • 1970-01-01
相关资源
最近更新 更多