我正在尝试从Peter Seibel的书" Practical Common Lisp"中学习Lisp。在chapter 8 : "Macros: Defining your own"中,我遇到了这个曾经只有一次的宏。在该页面的底部,给出了一个实现。现在这对我来说是一个非常复杂的宏,所以我看到this question on 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惯例,我试图以某种方式缩进代码,以便我能理解其中的内容,因为我是新手)
我测试这个宏的方式与我链接的章节中描述的方式非常相似,即。使用类似(随机100)的参数调用函数,这样如果它们被评估两次,结果将是错误的。我还通过macroexpand / macroexpand-1扩展了我的宏(以及我用过它的宏),这似乎也是正确的。
所以我想知道我的实现是否正确?或者有什么我想念的东西(我想这很可能)......
答案 0 :(得分: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
的宏)每次都使用相同的符号。如果您的宏调用嵌套(例如,您在另一个外部宏的使用体内使用外部宏),则符号将发生冲突。