这个宏有优势吗?
Is there an advantage to this macro?
我正在阅读 Peter Seibel 的 Practical Common Lisp。在 Chapter 9 中,他正在 reader 通过创建单元测试框架,他包含以下宏来确定列表是否仅由 true 表达式组成:
(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
,它基本上只是 inline-optimizing 可以由函数完成的事情.可以请求使用 (declaim 'inline ...)
内联函数。此外,我们可以使用 define-compiler-macro
为该函数编写一个编译器宏。使用该宏,我们可以生成扩展, 和 将其作为一个函数,我们可以 apply
或间接使用它。
函数内部计算结果的其他方式:
(not (position nil expressions))
(not (member nil expressions))
这个例子看起来确实像宏练习:制作一个gensym,并用loop
生成代码。 此外,宏是单元测试框架中可能出现的某些内容的起点。
在这种情况下,这可能无关紧要,但对于未来的版本,使用宏可能更有用。使用宏有意义吗?取决于用例:
使用函数
(combine-results (foo) (bar) (baz))
请注意,在 运行 时,Lisp 认为 combine-results
是一个函数。然后它评估参数。然后它使用结果值调用函数 combine-results
。此评估规则已硬编码到 Common Lisp 中。
这意味着:函数的代码 运行s 在参数被计算之后。
使用宏
(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
将是宏或 特殊运算符 ,它们确保一些代码 运行s before, around and after 我们提供。
我们将让代码执行 before、around 和 after 提供的表格。那有用吗?有可能。它可能对更广泛的 test-framework.
有用
如果这听起来很熟悉,那么您会发现可以使用 CLOS 方法(primary、before、after、around)获得类似的代码结构。测试将在主要方法中 运行,而其他代码将 运行 作为 around、before 和 在 方法之后。
请注意,宏也可以打印(参见 Hans23 的评论),检查 and/or 更改提供的表格。
我正在阅读 Peter Seibel 的 Practical Common Lisp。在 Chapter 9 中,他正在 reader 通过创建单元测试框架,他包含以下宏来确定列表是否仅由 true 表达式组成:
(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
,它基本上只是 inline-optimizing 可以由函数完成的事情.可以请求使用 (declaim 'inline ...)
内联函数。此外,我们可以使用 define-compiler-macro
为该函数编写一个编译器宏。使用该宏,我们可以生成扩展, 和 将其作为一个函数,我们可以 apply
或间接使用它。
函数内部计算结果的其他方式:
(not (position nil expressions))
(not (member nil expressions))
这个例子看起来确实像宏练习:制作一个gensym,并用loop
生成代码。 此外,宏是单元测试框架中可能出现的某些内容的起点。
在这种情况下,这可能无关紧要,但对于未来的版本,使用宏可能更有用。使用宏有意义吗?取决于用例:
使用函数
(combine-results (foo) (bar) (baz))
请注意,在 运行 时,Lisp 认为 combine-results
是一个函数。然后它评估参数。然后它使用结果值调用函数 combine-results
。此评估规则已硬编码到 Common Lisp 中。
这意味着:函数的代码 运行s 在参数被计算之后。
使用宏
(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
将是宏或 特殊运算符 ,它们确保一些代码 运行s before, around and after 我们提供。
我们将让代码执行 before、around 和 after 提供的表格。那有用吗?有可能。它可能对更广泛的 test-framework.
有用如果这听起来很熟悉,那么您会发现可以使用 CLOS 方法(primary、before、after、around)获得类似的代码结构。测试将在主要方法中 运行,而其他代码将 运行 作为 around、before 和 在 方法之后。
请注意,宏也可以打印(参见 Hans23 的评论),检查 and/or 更改提供的表格。