【问题标题】:Lisp: Macros vs Functions [duplicate]Lisp:宏与函数
【发布时间】:2012-07-20 16:26:15
【问题描述】:

为了完全理解如此强大的 lisp 宏,我想到了一个问题。我知道关于宏的一条黄金法则是:“当函数可以完成工作时,切勿使用宏”。 然而,阅读第 9 章 - Practical: Building a Unit Test Framework - 来自 Practical Common Lisp 一书,我被介绍到下面的宏,其目的是消除测试用例表达式的重复,以及随之而来的错误标记结果的风险。

;; Function defintion. 

(defun report-result (result form)
  (format t "~:[FAIL~;pass~] ... ~a~%" result form))

;; Macro Definition

(defmacro check (form)
  `(report-result ,form ',form))

好的,我理解它的目的,但我可以使用函数而不是宏来完成它,例如:

(setf unevaluated.form '(= 2 (+ 2 3)))

(defun my-func (unevaluated.form)
  (report-result (eval unevaluated.form) unevaluated.form))
  1. 这仅仅是因为给定的宏太简单了吗?
  2. 此外,由于代码本身(如控制结构、函数等)被表示为 LIST,Lisp 宏系统相对于它的对手来说是否如此强大?

【问题讨论】:

  • 这条“黄金法则”很愚蠢。在你认为合适的地方使用宏,忘记所有脑残的“规则”。至于您的示例,它几乎不是等效的,因为您将编译推迟到运行时。如果您的表单包含对某些本地范围名称的引用,则它根本不起作用。
  • Sk,你能举一个关于 eval 函数和本地范围名称的例子吗?
  • (let ((x 2)) (eval '(+ x x))) 根本不起作用,OTOH 如果这个(+ x x) 表单是由宏生成的,它会自然编译。
  • 这个 qst 首先出现在 google 搜索中,但它并没有真正解释 Lisp 宏和函数之间的区别。似乎问题主题行可以写得更好。 JMO。

标签: macros common-lisp


【解决方案1】:

但如果它是一个宏,你可以这样做:

(check (= 2 (+ 2 3)))

有了一个函数,你必须这样做:

(check '(= 2 (+ 2 3)))

此外,对于宏,(= 2 (+ 2 3)) 实际上是由编译器编译的,而对于函数,它是由 eval 函数评估的,不一定是同一件事。

附录:

是的,它只是评估函数。现在这意味着什么取决于实现。有些人可以解释它,有些人可以编译和执行它。但简单的事情是你不知道从一个系统到另一个系统。

其他人提到的空词环境也很重要。

考虑:

(defun add3f (form)
  (eval `(+ 3 ,form)))

(demacro add3m (form)
  `(+ 3 ,form))

然后观察:

[28]> (add3m (+ 2 3))
8
[29]> (add3f '(+ 2 3))
8
[30]> (let ((x 2)) (add3m (+ x 3)))
8
[31]> (let ((x 2)) (add3f '(+ x 3)))

*** - EVAL: variable X has no value
The following restarts are available:
USE-VALUE      :R1      Input a value to be used instead of X.
STORE-VALUE    :R2      Input a new value for X.
ABORT          :R3      Abort main loop
Break 1 [32]> :a

对于大多数用例来说,这真的很糟糕。由于 eval 没有词法环境,它无法从封闭的 let 中“看到”x

【讨论】:

  • 会,所以在这里使用 eval 函数我只是在评估表达式?
  • @uxtee:默认情况下,eval 在空词法环境中运行。这几乎不是你想要的。
  • 应该说defmacro而不是demacro
【解决方案2】:

更好的替换不是使用eval,它不会在所有情况下都按预期执行(例如,它无法访问词法环境),而且也有点矫枉过正(见这里:@987654321 @),但是使用匿名函数的东西,像这样:

(defun check (fn)
  (report-result (funcall fn) (function-body fn)))

CL-USER> (check (lambda () (= 2 (+ 2 3))))

顺便说一句,这就是在 Ruby 中完成这些事情的方式(匿名函数在那里称为procs)。

但是,正如您所见,它变得不那么优雅了(除非您添加语法糖),而且实际上还有一个更大的问题:Lisp 中没有 function-body 函数(尽管可能有非标准的方法来解决它)。总体而言,如您所见,对于这项特定任务,替代解决方案要差得多,尽管在某些情况下这种方法可能会奏效。

不过,一般来说,如果您想对传递给宏的表达式的源代码做一些事情(通常这是使用宏的主要原因),函数是不够的。

【讨论】:

    【解决方案3】:

    report-result 函数需要源代码和执行结果。

    CHECK 从单一来源表单中提供两者。

    如果您将一堆check 表单放入文件中,则可以使用编译Lisp 文件的通常过程轻松编译它们。您将获得检查代码的编译版本。

    使用函数和EVAL(最好使用COMPILE)您可以将源评估推迟到以后。它是否被解释或编译也不清楚。在编译的情况下,您稍后会得到编译器的检查。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-03-25
      • 1970-01-01
      • 2021-10-13
      • 1970-01-01
      • 1970-01-01
      • 2020-06-01
      • 1970-01-01
      相关资源
      最近更新 更多