gensym 符号如何捕获宏变量?
How can macro variable capture happen with a gensym symbol?
我正在学习普通的 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
returns是独一无二的,我无法在宏之外创建它的第二个副本。我假设捕获它是不需要的,否则规范版本不会费心添加 gensym
的附加层。捕获像 #:G826
这样的符号怎么会成为问题呢?请提供此捕获清单的示例。
我们可以证明 my-once-only
和 once-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
下,a
和 b
最终都成为完全相同 gensym
的 symbol-macrolet
别名;返回的表达式 (list a b)
最终变成类似于 (list #:g0025 #:g0025)
.
如果您正在编写一个实现一次性评估的宏编写助手,您不知道调用宏的代码将如何使用该符号,作者使用您的一次性工具。有两大未知数:宏的性质及其用途。
如您所见,如果您不制作新的 gensym,它将无法在所有可能的情况下正常工作。
我正在学习普通的 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
returns是独一无二的,我无法在宏之外创建它的第二个副本。我假设捕获它是不需要的,否则规范版本不会费心添加 gensym
的附加层。捕获像 #:G826
这样的符号怎么会成为问题呢?请提供此捕获清单的示例。
我们可以证明 my-once-only
和 once-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
下,a
和 b
最终都成为完全相同 gensym
的 symbol-macrolet
别名;返回的表达式 (list a b)
最终变成类似于 (list #:g0025 #:g0025)
.
如果您正在编写一个实现一次性评估的宏编写助手,您不知道调用宏的代码将如何使用该符号,作者使用您的一次性工具。有两大未知数:宏的性质及其用途。
如您所见,如果您不制作新的 gensym,它将无法在所有可能的情况下正常工作。