【问题标题】:Problem with macro behaviour in lisplisp 中的宏行为问题
【发布时间】:2011-02-23 17:07:42
【问题描述】:

如果在 REPL 中我这样做:

(dolist (x (1 2 3))
  (print x))

然后我得到一个错误,因为在 (1 2 3) 中,数字 1 不是符号或 lambda expr。 如果我这样做:

(dolist (x (list 1 2 3))
      (print x))

然后它工作正常。

我的问题是为什么以下工作:

REPL> (defmacro test (lst)
           (dolist (x lst)
             (print x)))
=> TEST
REPL> (test (1 2 3))
1
2
3
=>NIL

为什么 dolist 在宏定义中接受 (1 2 3) 而直接在 repl 中却不接受? 假设:

“由于 TEST 是一个宏,它不计算它的参数,所以 (1 2 3) 被原样传递给 dolist 宏。所以 dolist 必须像它在传递 (1 2 3) 时一样抱怨回复”

显然是错误的。但是在哪里?

更新:虽然答案有助于澄清对宏的一些误解,但我的问题仍然存在,我将尝试解释原因:

我们已经确定 dolist 会评估其列表参数(代码块 1、2)。好吧,在宏定义中调用它并且传递给它的列表参数是定义的宏参数之一(代码块 3)时,情况似乎并非如此。更多细节: 宏在调用时不会计算其参数。所以我的测试宏,当它被调用时,将保留列表参数,并在扩展时将它按原样传递给 dolist。然后在扩展时将执行 dolist(我的测试宏定义中没有反引号)。它将以 (1 2 3) 作为参数执行,因为这是传递给它的测试宏调用。那么为什么它不抛出错误,因为 dolist 试图评估它的列表参数,在这种情况下,它的列表参数 (1 2 3) 是不可评估的。我希望这能消除我的困惑。

【问题讨论】:

    标签: macros lisp common-lisp


    【解决方案1】:

    这个表格:

    (defmacro test (lst)
      (dolist (x lst)
        (print x)))
    

    定义了一个,它是一个“代码转换函数” 在宏扩展时使用此宏应用于表单。所以, 在你定义了这个宏之后,当你评估这个表达式时:

    (test (1 2 3))
    

    它首先被读取到这个列表中:

    (test (1 2 3))
    

    然后,由于 Lisp 在操作符位置读取test,它得到 通过将参数(即文字列表(1 2 3))传递给上面定义的宏扩展函数来进行宏扩展。这意味着 在宏扩展时会评估以下内容:

    (dolist (x '(1 2 3))
      (print x))
    

    因此,在宏扩展时,会打印三个值。最后, 该表单的返回值作为要编译的代码返回 并执行。 Dolist 在这里返回nil,所以这是返回的代码:

    nil
    

    Nil 计算结果为 nil,并返回。

    一般来说,这样的宏不是很有用。见“实用通用 Peter Seibel 的 Lisp”或 Paul Graham 的“On Lisp”作为介绍 到有用的宏。


    更新: 概括一下 阅读、扩展和评估 Lisp 代码。

    首先,REPL 接收一个字符流:(test ((1@9876543228@79 ) ),它组装成 令牌:(test(123))

    然后,这被翻译成符号树: (test (1 2 3))。这一步可能涉及到所谓的阅读器宏。 例如,'x 被翻译为 (quote x)。

    然后,从外向内,操作符位置的每个符号(即 检查表格中的第一个位置)。如果它命名一个宏,那么 使用 code 调用相应的宏函数(即, 符号的子树),这是形式的其余部分作为参数。这 宏函数应该返回一个新的形式,即 code,它 替换宏形式。在您的情况下,宏 test 获取代码 (123) 作为参数,打印其中包含的每个符号 (注意这甚至在编译时间之前),并返回nil, 扔掉它的论点(编译器甚至从来没有看到你的小 列表)。然后再次检查返回的代码是否可能 宏展开。

    最后,不包含任何宏调用的扩展代码 不再是评估,即编译和执行。 Nil 碰巧 成为自我评价的象征;它的计算结果为nil

    这只是一个粗略的草图,但我希望它能澄清一些事情。

    【讨论】:

    • 我不明白的是为什么我传递给宏调用的形式 (1 2 3) 在宏扩展时变为 '(1 2 3) 而不是保持 (1 2 3)
    • @Paralife:它仍然是(1 2 3),只是没有得到评估。我通过插入quote 来模拟实际评估,这会产生一个文字。
    • @Paralife:因为 DOLIST 宏扩展的代码恰好将对象 (1 2 3) 放置在将对其进行评估的位置。您可以使用(macroexpand-1 '(dolist (x (1 2 3)) (print x))) 的形式查看它。
    • 附带说明,DOLIST 对其参数列表进行评估,因为它非常有用。如果没有,则只能向其传递文字列表,但不能传递变量或函数结果。
    • @Paralife:不,DOLIST在宏展开时的list参数不是(1 2 3),而是lst。它评估 lst 得到 (1 2 3)。
    【解决方案2】:

    您的宏 test 不返回任何代码。是的,不评估宏参数。如果你想看到同样的错误,你必须将你的宏定义为:

    (defmacro 测试 (lst) `(dolist (x ,lst) (打印 x)))

    【讨论】:

      【解决方案3】:

      通常,当您对宏扩展有疑问时,参考MACROEXPAND-1 是很好的第一步。

      * (macroexpand-1 '(test (1 2 3)))
      
      1 
      2 
      3 
      NIL
      T
      

      IE,发生的是 实际 扩展是打印序列。

      Nil 是DOLIST 返回的内容,是展开后的代码。

      【讨论】:

      • 我的问题与在扩展时但在打印之前发生的事情有关。为什么 dolist 不抛出错误,因为它是用不可评估的参数调用的? (注意 dolist 评估它的 list 参数)。查看我的更新
      【解决方案4】:

      宏使它们的参数未经评估而通过。他们可以选择评估它们。 dolist 为其列表参数执行此操作。它适用于在宏 test 中为 lst 传入的未引用列表:

      (defmacro test (lst)
        (dolist (x lst)
          (print x)))
      

      这是因为在宏扩展时 dolistlst 视为其参数。所以当它评估它时,它会得到列表(1 2 3)

      【讨论】:

        【解决方案5】:

        lst 是一个变量,当展开宏测试时,也表示eval dolist 结构。第一步是评估表单 lst,将得到 lisp 对象 (1 2 3)。

        如下示例:

        (defmacro 测试 (a) (+ a 2))

        (测试 2) --> 4 ;意思是调用add函数,第一个变量a绑定一个值2。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-05-03
          • 1970-01-01
          • 2010-10-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多