【问题标题】:Is there an advantage to this macro?这个宏有优势吗?
【发布时间】:2016-05-03 22:31:55
【问题描述】:

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

宏的优势是否在于它在运行时对于在编译时扩展的任何调用更有效?还是它是一种范式?还是这本书只是想找借口在宏中练习不同的模式?

【问题讨论】:

  • 宏的真正好处是它可以访问原始表单,因此可以打印无法评估为真值的表单。很遗憾,书中的例子并没有真正表明这一点。
  • @hans23 : 在本书代码中,表单实际上是宏,用于打印结果。

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


【解决方案1】:

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

使用函数

(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 将是宏或特殊运算符,它们可以确保某些代码在之前, aroundafter 我们提供的嵌入表单。

我们会有代码执行beforearoundafter提供的表单。那有用吗?有可能。它可能对更广泛的测试框架有用。

如果这听起来很熟悉,那么您会发现使用 CLOS 方法(主要、之前、之后、周围)可以获得类似的代码结构。测试将在主要方法中运行,其他代码将作为 aroundbeforeafter 方法运行。

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

【讨论】:

    【解决方案2】:

    你的观察基本上是对的;事实上你的功能可以是:

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

    【讨论】:

    • 如果宏在评估其参数的过程中发生短路,那么它可能很有用(例如,用于优化),而函数方法绝不允许这样做。但在这种形式中它不是。
    • @JoaoTavora 如果宏短路,那将是标准and宏的冗余实现!
    • @Kaz,不完全是因为如果所有参数都不为零,它会返回t。但是,是的,基本上。我认为在这种情况下真正的用途是@hans23 的“冗长”角度:通过使用宏,您可以打印表单并使用表单本身做任何您想做的事情。
    • @JoaTavora;是的,所以这相当于在AND 的参数中再添加一个T 术语。严格返回T 为真的布尔表达式有一个优点:它们可以可靠地与EQ 进行比较。 (defmacro bool-and (&rest args) `(and ,*args t)).
    • @JoeTavora:我最近意识到这一点,当我在 Lisp 方言中更改名为 chr-isdigit 的库函数以返回数字值而不是 t,当字符是数字时(使函数类似于 CL 中的 digit-char-p 函数)。但是,这会破坏代码,例如[partition-by digit-char-p sequence],因为现在两个不同的连续数字构成不同的分区,因为结果不是equal
    猜你喜欢
    • 1970-01-01
    • 2015-01-06
    • 1970-01-01
    • 2010-09-12
    • 2019-11-01
    • 2011-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多