【问题标题】:How can macro variable capture happen with a gensym symbol?如何使用 gensym 符号捕获宏变量?
【发布时间】:2019-09-17 22:39:15
【问题描述】:

我正在学习普通的 lisp。我编写了一个版本的 once-only 宏,它遇到了异常的变量捕获问题。

我的宏是这样的:

(defmacro my-once-only (names &body body)
  (let ((syms (mapcar #'(lambda (x) (gensym))
                      names)))
    ``(let (,,@(mapcar #'(lambda (sym name) ``(,',sym ,,name))
                      syms names))
        ,(let (,@(mapcar #'(lambda (name sym) `(,name ',sym))
                      names syms))
           ,@body))))

only-once 的规范版本是这样的:

(defmacro once-only ((&rest names) &body body)
  (let ((gensyms (loop for n in names collect (gensym))))
    `(let (,@(loop for g in gensyms collect `(,g (gensym))))
      `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))
        ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
           ,@body)))))

据我所知,区别在于规范版本使用only-once 为宏的每次扩展生成新符号。例如:

CL-USER> (macroexpand-1 '(once-only (foo) foo))
(LET ((#:G824 (GENSYM)))
  `(LET (,`(,#:G824 ,FOO))
     ,(LET ((FOO #:G824))
        FOO)))
T
CL-USER> (macroexpand-1 '(my-once-only (foo) foo))
`(LET (,`(,'#:G825 ,FOO))
   ,(LET ((FOO '#:G825))
      FOO))
T

我的宏用于存储foo 值的变量对于此表单的每个扩展都是相同的,在本例中为#:G825。这类似于定义如下宏:

(defmacro identity-except-for-bar (foo)
  `(let ((bar 2))
     ,foo))

这个宏捕获bar,当bar被传递给它时,这个捕获就会显现出来,就像这样:

CL-USER> (let ((bar 1))
           (identity-except-for-bar bar))
2

但是,我想不出任何方法可以将 #:G825 传递给使用 my-only-once 的宏,使其像这样中断,因为符号 gensym 返回是唯一的,我无法创建它的第二个副本宏之外。我认为捕获它是不需要的,否则规范版本不会费心添加gensym 的附加层。捕获像#:G826 这样的符号怎么会有问题?请提供此捕获清单的示例。

【问题讨论】:

  • 有时宏“overgensym”的情况。许多使用 (gensym) 的 Lisp 宏可以使用静态生成符号来编写。但是,那么作者必须证明没有错。
  • 此外,如果两个不同的符号具有相同的打印表示,调试会变得非常混乱。

标签: macros lisp common-lisp


【解决方案1】:

我们可以证明my-once-onlyonce-only 之间的行为差​​异:

让我们将测试表单存储在一个变量中。

(defvar *form* '(lexalias a 0 (lexalias b (1+ a) (list a b))))

这个测试表单执行一个名为lexalias 的宏,我们将以两种方式定义它。首先是once-only

(defmacro lexalias (var value &body body)
  (once-only (value)
    `(symbol-macrolet ((,var ,value))
       ,@body)))

(eval *form*) -> (0 1)

然后my-once-only:

(defmacro lexalias (var value &body body)
  (my-once-only (value)
    `(symbol-macrolet ((,var ,value))
       ,@body)))

(eval *form*) -> (1 1)

哎呀!问题是在my-once-only 下,ab 最终都成为symbol-macrolet 完全相同gensym 的别名;返回的表达式(list a b) 最终会变成(list #:g0025 #:g0025)

如果您正在编写一个实现一次性评估的宏编写助手,您不知道调用宏的代码将如何使用该符号,其作者使用您的一次性评估工具。有两个很大的未知数:宏的性质及其用途。

如您所见,如果您不制作新的 gensym,它将无法在所有可能的场景中正常工作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多