使用gensym符号如何进行宏变量捕获?

ego*_*ego 4 lisp macros common-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))))
Run Code Online (Sandbox Code Playgroud)

的标准版本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)))))
Run Code Online (Sandbox Code Playgroud)

据我所知,不同之处在于规范版本使用会为宏的每次扩展生成新的符号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
Run Code Online (Sandbox Code Playgroud)

我的宏用于存储的值的变量foo在此格式的每次扩展中都是相同的,在这种情况下为#:G825。这类似于定义如下的宏:

(defmacro identity-except-for-bar (foo)
  `(let ((bar 2))
     ,foo))
Run Code Online (Sandbox Code Playgroud)

此宏捕获bar,并且此捕获显示了何时bar将其传递给它,如下所示:

CL-USER> (let ((bar 1))
           (identity-except-for-bar bar))
2
Run Code Online (Sandbox Code Playgroud)

但是,我无法想到任何传递#:G825给使用的宏的方法,my-only-once这样它会像这样中断,因为符号gensym返回是唯一的,并且我无法在宏之外创建它的第二个副本。我假设捕获它是多余的,否则规范版本不会费心地添加的附加层gensym。如何捕获类似的符号#:G826会成为问题?请提供一个显示捕获的示例。

Kaz*_*Kaz 5

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

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

(defvar *form* '(lexalias a 0 (lexalias b (1+ a) (list a b))))
Run Code Online (Sandbox Code Playgroud)

该测试表单将执行一个名为的宏lexalias,我们将通过两种方式对其进行定义。首先once-only

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

(eval *form*) -> (0 1)
Run Code Online (Sandbox Code Playgroud)

然后用my-once-only

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

(eval *form*) -> (1 1)
Run Code Online (Sandbox Code Playgroud)

糟糕!问题在于my-once-only,两者ab最终都是symbol-macrolet完全相同的别名gensym; 返回的表达式(list a b)最终像是(list #:g0025 #:g0025)

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

如您所见,如果不制作新的gensyms,它将无法在所有可能的情况下正常工作。