2

我正在尝试从 Peter Seibel 的书“Practical Common Lisp”中学习 Lisp。在第 8 章:“宏:定义你自己的”中,我遇到了这个只有一次的宏。在该页面的底部,给出了一个实现。现在这对我来说是一个非常复杂的宏,所以我在 stackoverflow 上看到了这个问题,那里有一些很好的解释。

但是,即使我(仍然)还没有完全理解宏,我也理解了它的用途。所以我尝试编写自己的实现:

(defmacro my-once-only ((&rest names) &body body)
  (let
    (
      (gensyms (loop for n in names collect (gensym)))
    )

    `(list 'let
      (list ,@(loop for n in names for g in gensyms collect `(list ',g ,n)))

      (let
        ,(loop for n in names for g in gensyms collect `(,n ',g))

        ,@body))))

(如果我没有遵循标准的 lisp 缩进约定,请原谅我,我试图以某种方式缩进代码,以便我可以理解去哪里,因为我是新手)

我测试这个宏的方式与我链接的那一章中描述的方式大致相同,即。使用 (random 100) 之类的参数调用函数,这样如果对它们进行两次评估,结果将是错误的。我还通过macroexpand/macroexpand-1 扩展了我的宏(以及我使用它的宏),这似乎也是正确的。

所以我想知道我的实现是否正确?还是我遗漏了什么(我可能认为)......

4

1 回答 1

4

让我们实际对这两个实现进行宏展开,看看它们有何不同:

* (macroexpand '(once-only (foo bar) (+ foo bar)))
(LET ((#:G619 (GENSYM)) (#:G620 (GENSYM)))
  `(LET ((,#:G619 ,FOO) (,#:G620 ,BAR))
     ,(LET ((FOO #:G619) (BAR #:G620))
        (+ FOO BAR))))

* (macroexpand '(my-once-only (foo bar) (+ foo bar)))
(LIST 'LET (LIST (LIST '#:G621 FOO) (LIST '#:G622 BAR))
      (LET ((FOO '#:G621) (BAR '#:G622))
        (+ FOO BAR)))

让我们将您的宏扩展重写为更容易让 Lisper 阅读的内容:

`(LET ((#:G621 ,FOO) (#:G622 ,BAR))
   ,(LET ((FOO '#:G621) (BAR '#:G622))
      (+ FOO BAR)))

请注意,您的版本缺少附加的gensym. 这意味着每次调用外部宏(正在使用的宏my-once-only)每次都使用相同的符号。如果您的宏调用嵌套(例如,您在使用外部宏的主体内使用外部宏),符号将发生冲突。

于 2014-04-23T00:02:38.993 回答