这个宏有优势吗?

时间:2016-01-27 05:23:22

标签: macros lisp common-lisp practical-common-lisp

我正在阅读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))

宏的优点是它在运行时对于在编译时扩展的任何调用更有效吗?或者它是一个范例的东西?或者这本书只是试图找借口在宏中练习不同的模式?

2 个答案:

答案 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-environmentembed-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生成代码。 此外,宏是可能出现在单元测试框架中的起点。