Macros 列出未在宏内计算的gensym符号

Macros 列出未在宏内计算的gensym符号,macros,lisp,common-lisp,Macros,Lisp,Common Lisp,我正在尝试编写一个宏,它接受一个变量列表和一个代码体,并确保在执行代码体后变量恢复为其原始值(Paul Graham的ANSI Common Lisp中的练习10.6) 然而,我不清楚为什么我的gensym会在一个地方按我的预期进行评估,而不是在另一个类似的地方(注意:我知道有一个更好的解决方案。我只想弄清楚评估中的差异) 这里是第一个定义,其中lstgensym计算为传递给mapcar的lambda中的列表: (defmacro exec-reset-vars-1 (vars body)

我正在尝试编写一个宏,它接受一个变量列表和一个代码体,并确保在执行代码体后变量恢复为其原始值(Paul Graham的ANSI Common Lisp中的练习10.6)

然而,我不清楚为什么我的
gensym
会在一个地方按我的预期进行评估,而不是在另一个类似的地方(注意:我知道有一个更好的解决方案。我只想弄清楚评估中的差异)

这里是第一个定义,其中
lst
gensym计算为传递给
mapcar
lambda
中的列表:

(defmacro exec-reset-vars-1 (vars body)
  (let ((lst (gensym)))
    `(let ((,lst ,(reduce #'(lambda (acc var) `(cons ,(symbol-value var) ,acc))
                          vars
                          :initial-value nil)))
         ,@body
         ,@(mapcar #'(lambda (var) `(setf ,var (car ,lst)))
                   vars))))
但是,尽管它的工作原理与我预期的完全一致,但它并不是一个正确的解决方案,因为在尝试重置值时,我总是抓住
lst
的第一个元素。我真的想映射2个列表。所以现在我写:

(defmacro exec-reset-vars-2 (vars body)
  (let ((lst (gensym)))
    `(let ((,lst ,(reduce #'(lambda (acc var) `(cons ,(symbol-value var) ,acc))
                          vars
                          :initial-value nil)))
         ,@body
         ,@(mapcar #'(lambda (var val) `(setf ,var ,val))
                   vars
                   lst))))

但是现在我得到一个错误,它说
#:G3984
不是一个列表。如果我用
(符号值lst)
替换它,我会得到一个错误,说变量没有值。但为什么不呢?为什么它在
lambda
中的
setf
中有一个值,而不是作为传递给
mapcar
的参数?

在宏扩展时,您尝试映射
lst
的值,这是当时的一个符号。所以这毫无意义

尝试获取符号值也毫无意义,因为
lst
的绑定是词法绑定,而
symbol-value
不是访问该绑定的方法。其他绑定在当时不可用

显然,
lst
在宏扩展时有一个值:一个符号。这是你在lambda里面看到的

您需要明确哪些值是在宏扩展时计算的,哪些值是在运行时计算的

关于命名的建议:

  • lst
    在Lisp中是一个糟糕的名称,请使用
    list
  • 名称
    lst
    毫无意义,因为它的值不是一个列表,而是一个符号。我称之为
    列表变量符号
    。看起来很长,不是吗?但这要清楚得多。现在,您将看到它是一个符号,用作保存列表的变量的名称

    • 我敢肯定你想得太多了。想象一下:

      (defparameter *global* 5)
      (let ((local 10))
        (with-reset-vars (local *global*)
          (setf *global* 20)
          (setf local 30)
          ...))
      
      我认为扩展非常简单:

      (defparameter *global* 5)
      (let ((local 10))
        (let ((*global* *global*) (local local))
          (setf *global* 20)
          (setf local 30)
          ...)
        (print local)) ; prints 10
      (print *global*)  ; prints 5 
      
      自行重置,因此您可以看到宏应该非常简单,只需使用
      进行阴影绑定,除非我误解了赋值


      你过于复杂的宏做了非常糟糕的事情。例如获取全局符号的编译时间值,这会将它们重置为函数使用此值的时间,而不是在正文之前。

      感谢您的回答。你完全正确。正如我所指出的,我知道这不是一个很好的解决方案,我只是想知道为什么gensym会在一个地方评估它,而不是在另一个地方。在第一个阶段中,您执行
      (car lst gensym symbol)
      ,它提供了运行时绑定的
      let
      -绑定的第一部分,而在第二个阶段中,您尝试在宏扩展时间映射相同的内容,但在第三阶段,它不是列表,而是一个符号值。宏本质上是代码转换,您不应该依赖变量绑定。谢谢您的回复。为了确保正确理解,在这两种情况下,
      lst
      在宏扩展期间具有相同的值-一个符号。区别在于,在第一种情况下,展开
      (car,lst)
      ,然后在运行时评估其展开形式,此时
      lst
      是一个列表;而在第二个例子中,作为
      mapcar
      的参数,它在宏扩展期间被评估。这是否正确?@UnixOne:No.在运行时没有
      lst
      ,但变量的名称是宏扩展时的
      lst
      值。