Lisp:宏与函数

时间:2012-07-21 20:11:11

标签: macros common-lisp

在我完全理解如此强大的lisp宏的过程中,我想到了一个问题。我知道关于宏的一条黄金法则就是说“当一个函数完成工作时不要使用宏”。 然而阅读第9章 - Practical: Building a Unit Test Framework - 从实用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. 此外,由于代码本身(如控制结构,功能等),Lisp Macro System相对于其对手如此强大 - 是否表示为LIST?

3 个答案:

答案 0 :(得分:12)

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

(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没有词汇环境,因此无法从封闭的x“看到”let

答案 1 :(得分:4)

更好的替换不是eval,它不会按预期执行所有情况(例如,它无法访问词法环境),并且也是矫枉过正(请参阅此处: https://stackoverflow.com/a/2571549/977052),但使用匿名函数的东西,如:

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

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

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

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

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

答案 2 :(得分:3)

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

CHECK从单一来源表单提供。

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

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