在我完全理解如此强大的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))
答案 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
),您可能会将源评估推迟到以后的时间。它是否被解释或编译也不清楚。在编译的情况下,稍后您将获得编译器的检查。