Lisp 为什么会出现函数/宏二分法?

Lisp 为什么会出现函数/宏二分法?,lisp,common-lisp,Lisp,Common Lisp,为什么Common Lisp中存在函数/宏二分法 允许使用同一名称表示宏(当在编译/评估中的函数位置找到宏时优先)和函数(例如可用于mapcar)时,有什么逻辑问题 例如,将second定义为宏和函数将允许使用 (setf (second x) 42) 及 无需创建任何setf欺骗 当然,很明显,宏可以做的不仅仅是函数,因此类比是不完整的(当然,我不认为每个宏也应该是函数),但为什么要禁止它呢?如果它可能有用,为什么要让它们共享一个名称空间呢 我希望我没有冒犯任何人,但我真的没有找到一个“为什

为什么Common Lisp中存在函数/宏二分法

允许使用同一名称表示宏(当在编译/评估中的函数位置找到宏时优先)和函数(例如可用于
mapcar
)时,有什么逻辑问题

例如,将
second
定义为宏和函数将允许使用

(setf (second x) 42)

无需创建任何
setf
欺骗

当然,很明显,宏可以做的不仅仅是函数,因此类比是不完整的(当然,我不认为每个宏也应该是函数),但为什么要禁止它呢?如果它可能有用,为什么要让它们共享一个名称空间呢

我希望我没有冒犯任何人,但我真的没有找到一个“为什么那样做?”的回答真的相关。。。我在寻找为什么这是个坏主意。因为没有好的用途而强加任意的限制在我看来有点傲慢(有点假设有完美的远见)


或者,允许它存在实际问题吗?

因为完全相同的名称将代表两个不同的对象,具体取决于上下文。它使程序不必要地难以理解。

我认为Common Lisp的两个名称空间(函数和值),而不是三个名称空间(宏、函数和值),是一种历史偶然性

早期的Lisp(20世纪60年代)以不同的方式表示函数和值:值作为运行时堆栈上的绑定,函数作为附加到符号表中符号的属性。这种实现上的差异导致了在20世纪80年代标准化CommonLisp时指定了两个名称空间。有关这一决定的解释,请参阅理查德·加布里埃尔的论文

宏(及其祖先,即不计算其参数的函数)以与函数相同的方式存储在符号表中的许多Lisp实现中。如果指定了第三个名称空间(用于宏),那么这些实现将很不方便,并且会导致许多程序的向后兼容性问题

有关FEXPR、宏和其他特殊形式的历史,请参阅Kent Pitman的论文


(注意:Kent Pitman的网站不适合我,所以我通过archive.org链接到了这些论文。)

宏和函数是两个截然不同的东西:

  • 宏正在使用源代码(!!!)并正在生成新的源代码(!!!)

  • 函数是参数化的代码块

现在我们可以从几个角度来看待这一点,例如:

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
。但事实并非如此。我猜,在设计CommonLisp时,还不清楚它应该做什么以及如何工作。如果
FOO
是一个函数,那么很明显:Common Lisp从Scheme中汲取了词汇范围的一流函数的思想,并将其集成到语言中

但是一流的宏呢?在设计了CommonLisp之后,一系列的研究都进入了这个问题并对其进行了研究。但在Common Lisp设计时,没有广泛使用一流的宏,也没有设计方法的经验。Common Lisp标准化了当时已知的内容以及语言用户认为开发软件所必需的内容(基于早期使用类似对象系统的经验,对象系统CLOS有点新颖)。Common Lisp的设计目的并不是为了拥有理论上最令人愉悦的Lisp方言,而是为了拥有一个功能强大的Lisp,从而能够高效地实现软件

我们可以解决这个问题,并说,传递宏是不可能的。开发人员必须以相同的名称提供一个函数,我们将传递该函数

但是
(funcall#'foo 12)
(foo 12)
会调用不同的机制吗?在第一种情况下,函数
foo
,在第二种情况下,我们使用宏
foo
为我们生成代码?真正地我们(作为人类程序员)想要这个吗?我不这么认为——看起来它让编程变得更加复杂
(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)))
1> (sqrt 4.0)
2.0
2> (defmacro sqrt (x :env e :form f)
     (if (constantp x e)
       (sqrt x)
       f))
** warning: (expr-2:1) defmacro: defining sqrt, which is also a built-in defun
sqrt
3> (sqrt 4.0)
2.0
4> (macroexpand '(sqrt 4.0))
2.0
5> (macroexpand '(sqrt x))
(sqrt x)
1> (define-place-macro foo (x) ^(car ,x))
foo
2> (macroexpand '(foo a)) ;; not a macro!
(foo a)
3> (macroexpand '(set (foo a) 42)) ;; just a place macro
(sys:rplaca a 42)
4> (define-place-macro foo (x) ^(bar ,x))
foo
5> (macroexpand '(foo a))
(foo a)
6> (macroexpand '(set (foo a) 42))
** (bar a) is not an assignable place