列出不在宏内部求值的 gensym 符号

List gensym symbol not evaluating inside macro

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

但是,我不清楚为什么我的 gensym 在一个地方的评估结果与我预期的一样,但在另一个类似的地方却没有(注意:我知道有一个更好的练习解决方案。我只是想弄清楚为什么评价不同)。

这是第一个定义,其中 lst gensym 求值为 lambda 内部的列表传递给 mapcar:

(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 不是列表。如果我用 (symbol-value lst) 替换它,我会收到一条错误消息,指出变量没有值。但为什么不呢?为什么它在 lambda 中的 setf 中有一个值,而不是作为传递给 mapcar 的参数?

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

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

显然 lst 在宏展开时有一个值:一个符号。这就是您在 lambda 中看到的内容。

您需要弄清楚哪些值是在宏展开时计算的,哪些是在运行时计算的。

关于命名的建议:

  • lst 在 Lisp 中是一个糟糕的名字,使用 list
  • 名称lst没有意义,因为它的值不是一个列表,而是一个符号。我称之为 list-variable-symbol。看起来很长,不是吗?但它更清楚。你现在会认为它是一个符号,用作变量持有列表的名称。

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

(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 

let 自己进行重置,所以您会发现宏应该非常简单,只需使用 let 进行阴影绑定,除非我误解了赋值。

你过于复杂的宏会做很糟糕的事情。就像获取全局符号编译时间的值一样,这会将它们重置为使用它而不是主体之前的函数的时间。