Macros 这个宏有什么优势吗?

Macros 这个宏有什么优势吗?,macros,lisp,common-lisp,practical-common-lisp,Macros,Lisp,Common Lisp,Practical Common Lisp,我正在读Peter Seibel的实用公共Lisp。在中,他正在引导读者创建一个单元测试框架,并包括以下宏以确定列表是否仅由真正的表达式组成: (defmacro combine-results (&body forms) (let ((result (gensym))) `(let ((,result t)) ,@(loop for form in forms collect `(unless ,form (setf ,result nil)))

我正在读Peter Seibel的实用公共Lisp。在中,他正在引导读者创建一个单元测试框架,并包括以下宏以确定列表是否仅由真正的表达式组成:

(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))

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

你的观察基本上是正确的;事实上,您的功能可以是:

(defun combine-results (&rest expressions)
  (every #'identity expressions))  ;; i.e. all expressions are true?
由于宏从左到右无条件地计算其所有参数,如果所有参数都为真,则生成
T
,因此它基本上只是内联优化函数可以完成的某些操作。可以请求函数与
内联(declaim'inline…
)。此外,我们可以使用
define compiler macro
为函数编写编译器宏。使用该宏,我们可以生成扩展,并将其作为一个函数,我们可以
应用
,或者间接应用

计算函数内部结果的其他方法:

(not (position nil expressions))
(not (member nil expressions))

这个例子看起来确实像宏实践:制作一个gensym,并使用
循环生成代码。此外,宏是单元测试框架中可能出现的某些内容的起点。

在这种情况下,这可能无关紧要,但对于未来的版本,使用宏可能更有用。使用宏有意义吗?取决于用例:

使用函数

(combine-results (foo) (bar) (baz))
请注意,在运行时Lisp看到,
combineresults
是一个函数。然后计算参数。然后调用函数
combineresults
和结果值。此求值规则硬编码到Common Lisp中

这意味着:函数的代码在对参数求值后运行

使用宏

(combine-results (foo) (bar) (baz))
因为Lisp看到它是一个宏,所以它在宏展开时调用宏并生成代码。生成的代码完全取决于宏。这允许我们生成如下代码:

(prepare-an-environment

  (embed-it (foo))
  (embed-it (bar))
  (embed-it (baz))

  (do-post-processing))
然后将执行此代码。例如,您可以设置系统变量,提供错误处理程序,设置一些报告机制,等等。每个单独的表单也可以嵌入到其他表单中。在函数运行之后,可以进行一些清理、报告等。
准备一个环境
嵌入它
将是宏或特殊操作符,它们确保一些代码在我们提供的嵌入表单之前、周围和之后运行

我们将在提供的表单之前、周围和之后执行代码。这有用吗?可能是。它对于更广泛的测试框架可能有用

如果这听起来很熟悉,那么您将看到可以使用CLOS方法(primary、before、after、around)获得类似的代码结构。测试将在主方法中运行,其他代码将在方法前后运行


请注意,宏还可以打印(请参见Hans23的注释)、检查和/或更改提供的表单。

如果宏在参数求值过程中短路(函数方法不允许),则宏可能会有用(例如,用于优化)。但是在这种形式下它不是。如果宏短路,它将是标准
宏的冗余实现@Kaz,因为如果所有参数都是非nil,它将返回
t
。但基本上是的。我认为在这种情况下,真正的用途是@hans23的“冗长”角度:通过使用宏,您可以打印表单,并对表单本身执行任何操作。@JoaTavora;是的,因此它相当于在
的参数中加上一个
T
项。严格返回
T
为true的布尔表达式有一个优点:它们可以可靠地与
EQ
进行比较
(defmacro bool and(&rest args)`(and,*args t))
@JoeTavora:最近,当字符是数字时,我用Lisp方言更改了一个名为
chr isdigit
的库函数,以返回数字值,而不是
t
(使该函数类似于CL中的
digit-char-p
函数). 但是,这破坏了代码,例如
[按数字划分-char-p序列]
,因为现在两个不同的连续数字构成了不同的分区,由于结果不等于
。宏的真正好处是它可以访问原始表单,因此可以将计算失败的表单打印为真实值。遗憾的是书中的示例并没有实际显示这一点。@hans23:在书中的代码中,表单实际上是宏,用于打印结果。