【问题标题】:List gensym symbol not evaluating inside macro列出 gensym 符号不在宏内部评估
【发布时间】:2016-06-25 17:36:31
【问题描述】:

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

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

这是lst gensym 计算为传递给mapcarlambda 内的列表的第一个定义:

(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 的参数?

【问题讨论】:

    标签: macros lisp common-lisp


    【解决方案1】:

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

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

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

    您需要明确在宏展开时计算哪些值以及在运行时计算哪些值。

    关于命名的建议:

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

    【讨论】:

    • 感谢您的回复。为了确保我理解正确,在这两种情况下lst 在宏扩展期间具有相同的值 - 一个符号。不同之处在于,在第一种情况下,(car ,lst) 被扩展,然后在运行时评估其扩展形式,此时lst 是一个列表;而在第二个中,作为mapcar 的参数,它在宏扩展期间被评估。这是正确的吗?
    • @UnixOne: 不。在运行时没有lst,但在宏扩展时名称为lst 的变量。
    【解决方案2】:

    我很确定你想多了。想象一下:

    (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 进行影子绑定,除非我误解了分配。

    您过于复杂的宏会做一些坏事。就像获取全局符号编译时间的值一样,这会将它们重置为使用 this 的函数的时间,而不是在正文之前。

    【讨论】:

    • 感谢您的回复。你是绝对正确的。正如我所指出的,我知道这不是一个很好的练习解决方案,我只是想知道为什么 gensym 评估它的一个地方而不是另一个地方。
    • @UnixOne 这很简单。在第一个中,您执行(car lst-gensym-symbol),它为您提供了在运行时绑定的let-binding 的第一部分,而在第二个中,您尝试在宏扩展时间内映射相同的部分,但在该阶段它不是一个列表而是一个符号值。宏本质上是代码转换,你不应该依赖变量绑定。
    猜你喜欢
    • 1970-01-01
    • 2015-05-25
    • 1970-01-01
    • 2012-11-19
    • 1970-01-01
    • 2020-06-11
    • 2012-03-10
    • 1970-01-01
    • 2017-05-19
    相关资源
    最近更新 更多