宏和函数是两个非常不同的东西:
宏使用源代码(!!!)并生成新的源代码(!!!)
函数是带参数的代码块。
现在我们可以从几个角度来看待这个问题,例如:
a) 我们如何设计一种语言,使函数和宏在我们的源代码中清晰可辨并且看起来不同,以便我们(人类)可以轻松地看出哪个是哪个?
或者
b) 我们如何混合宏和函数,以便结果最有用,并具有控制其行为的最有用规则? 对于用户而言,使用宏或函数不应有任何区别。
我们确实需要让自己相信b)才是正确的方向,并且我们希望使用一种语言,其中宏和函数的使用看起来相同,并按照类似的原则工作。拿船和汽车来说,它们看起来不同,它们的用例大多不同,它们运输人员 - 我们现在是否应该确保它们的交通规则大多相同,还是应该使它们不同,或者应该为它们特殊的用途设计规则?
对于函数,我们有如下的问题:定义函数、函数的作用域、函数的生命周期、传递函数、返回函数、调用函数、函数阴影、函数扩展、删除函数定义、函数的编译和解释等等。
如果我们要使宏看起来与函数大多相似,我们需要为它们解决上述大部分或全部问题。
在您的示例中,您提到了一个SETF表单。 SETF是一个宏,在宏展开时分析封闭的表单并为setter生成代码。这与SECOND是否为宏没有什么关系。在这种情况下,令SECOND成为宏也毫无帮助。
那么,有什么问题的例子吗?
(defmacro foo (a b)
(if (and (numberp b) (zerop b))
a
`(- ,a ,b)))
(defun bar (x list)
(mapcar #'foo (list x x x x) '(1 2 3 4)))
那么这应该做什么呢?直觉上看起来很容易:在列表上映射FOO
。 但这并不是这样的。 当Common Lisp被设计时,我会猜测,这并不清楚它应该做什么以及如何工作。 如果FOO
是一个函数,那么就很清楚了:Common Lisp采用了Scheme背后的基于词法范围的一级函数的思想,并将其融入了语言中。
但是一级宏呢?Common Lisp设计后,大量的研究针对这个问题进行了调查。 但是在Common Lisp设计的时候,还没有广泛使用一级宏,并且没有设计方法的经验。 Common Lisp正在标准化当时所知道的内容和语言用户认为必要的软件开发(CLOS对象系统有点新颖,基于类似对象系统的早期经验)。 Common Lisp的设计不是为了拥有理论上最令人愉悦的Lisp方言 - 它的设计目的是拥有一个功能强大的Lisp,允许有效地实现软件。
我们可以绕过这个问题并说,不能传递宏。开发人员必须提供同名的函数,我们将其传递。
但是(funcall#'foo 1 2)
和(foo 1 2)
会调用不同的机制吗? 在第一种情况下,函数foo
,而在第二种情况下,我们使用宏foo
为我们生成代码? 真的吗? 我们(作为人类程序员)想要这样吗? 我认为不是 - 它看起来使编程更加复杂。
从实用的角度来看:宏及其背后的机制已经足够复杂,以至于大多数程序员在实际代码中处理它时都有困难。 它们使人类的调试和代码理解变得更加困难。 表面上看,宏使代码更易读,但代价是需要理解代码扩展过程和结果。 找到一种进一步将宏集成到语言设计中的方法并不容易。
readscheme.org提供了与Scheme相关的宏研究方面的一些指针:Macros
那么Common Lisp呢
Common Lisp提供了可以被存储、传递等的一级函数,并为它们提供词法范围的命名(DEFUN
,FLET
,LABELS
,FUNCTION
,LAMBDA
)。
Common Lisp提供了全局宏(DEFMACRO
)和局部宏(MACROLET
)。
Common Lisp提供了全局编译器宏(DEFINE-COMPILER-MACRO)。
使用编译器宏,可以为符号提供函数或宏,还可以提供编译器宏。 Lisp系统可以决定优先考虑编译器宏而不是宏或函数。 它也可以完全忽略它们。 这个机制通