我正在阅读Peter Seibel的Practical Common Lisp。在Chapter 9中,他正在通过创建单元测试框架来引导读者,并且他包含以下宏来确定列表是否仅由真正的表达式组成:
(defmacro combine-results (&body forms)
(let ((result (gensym)))
`(let ((,result t))
,@(loop for form in forms collect `(unless ,form (setf ,result nil)))
,result)))
我不清楚使用宏的优势在于什么 - 似乎以下内容更清晰,动态值更有效:
(defun combine-results (&rest expressions)
(let ((result t))
(loop for expression in expressions do (unless expression (setf result nil)))
result))
宏的优点是它在运行时对于在编译时扩展的任何调用更有效吗?或者它是一个范例的东西?或者这本书只是试图找借口在宏中练习不同的模式?
答案 0 :(得分:9)
在这种情况下,它可能无关紧要,但对于将来的版本,使用宏可能更有用。使用宏是否有意义?取决于用例:
使用功能
(combine-results (foo) (bar) (baz))
请注意,在运行时,Lisp看到combine-results
是一个函数。然后它评估参数。然后它使用结果值调用函数combine-results
。此评估规则已硬编码到Common Lisp中。
这意味着:在评估参数之后,函数的代码运行。
使用宏
(combine-results (foo) (bar) (baz))
由于Lisp发现它是一个宏,它在宏扩展时调用宏并生成代码。生成的代码是什么,完全取决于宏。这允许我们生成如下代码:
(prepare-an-environment
(embed-it (foo))
(embed-it (bar))
(embed-it (baz))
(do-post-processing))
然后将执行此代码。因此,例如,您可以设置系统变量,提供错误处理程序,设置一些报告机制等。然后,每个单独的表单也可以嵌入到其他形式中。在函数运行后,可以执行一些清理,报告等。prepare-an-environment
和embed-it
将是宏或特殊运算符,这可确保某些代码运行< em>在之前,在之前和之后我们提供的嵌入式表单。
我们会在之前执行,在之后执行,在提供的表单之后执行。这有用吗?有可能。它可能对更广泛的测试框架有用。
如果这听起来很熟悉,那么你会发现使用CLOS方法可以获得类似的代码结构(主要,前,后,周围)。测试将在主方法中运行,其他代码将在 之前运行,在之前运行,在方法之后运行。
请注意,宏也可以打印(参见Hans23的评论),检查和/或改变提供的表格。
答案 1 :(得分:7)
你的观察基本上是正确的;事实上你的功能可以是:
(defun combine-results (&rest expressions)
(every #'identity expressions)) ;; i.e. all expressions are true?
由于宏无条件地从左到右评估它的所有参数,并且如果所有参数都为真,则产生T
,它基本上只是内联优化,可以由函数完成。可以请求使用(declaim 'inline ...)
内联函数。此外,我们可以使用define-compiler-macro
为函数编写编译器宏。使用该宏我们可以生成扩展,和将此作为一个函数,我们可以apply
和其他间接的函数。
计算函数内部结果的其他方法:
(not (position nil expressions))
(not (member nil expressions))
示例看起来像宏练习:制作gensym,并使用loop
生成代码。 此外,宏是可能出现在单元测试框架中的起点。