为什么功能/宏观二分法?

时间:2011-07-30 12:45:42

标签: lisp common-lisp

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

允许同一名称同时代表一个宏(在编译/评估时在函数位置找到优先级)和一个函数(例如用mapcar可用)时会出现什么逻辑问题?

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

(setf (second x) 42)

(mapcar #'second L)

无需创建任何setf诡计。

当然很明显,宏可以做的不仅仅是函数,所以类比不能完整(我当然不认为每个宏也是一个函数),但为什么禁止它呢?通过在可能有用的时候共享一个命名空间?

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

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

4 个答案:

答案 0 :(得分:13)

宏和函数是两个非常不同的东西:

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

  • 函数是参数化代码块。

现在我们可以从几个角度来看这个问题,例如:

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对宏相关研究提出了一些建议。方案:Macros

Common Lisp

怎么样?

Common Lisp提供的函数可以是第一类(存储,传递,...)和词法范围命名(DEFUNFLETLABELS,{{ 1}},FUNCTION)。

Common Lisp提供全局宏(LAMBDA)和本地宏(DEFMACRO)。

Common Lisp提供全局compiler macrosDEFINE-COMPILER-MACRO)。

使用编译器宏,可以为符号和编译器宏提供函数或宏。 Lisp系统可以决定在宏或函数上优先使用编译器宏。它也可以完全忽略它们。此机制主要用于用户编程特定的优化。因此,它没有解决任何与宏相关的问题,但提供了一种编程全局优化的实用方法。

答案 1 :(得分:10)

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

Early Lisps(在20世纪60年代)以不同的方式表示函数和值:值作为运行时堆栈上的绑定,以及作为附加到符号表中的符号的属性的函数。当Common Lisp在20世纪80年代被标准化时,这种实现的差异导致了两个命名空间的规范。有关此决定的解释,请参阅Richard Gabriel的论文Technical Issues of Separation in Function Cells and Value Cells

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

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

(注意:Kent Pitman的网站不适合我,因此我通过archive.org链接到论文。)

答案 2 :(得分:2)

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

答案 3 :(得分:1)

我的TXR Lisp方言允许符号同时成为宏和函数。而且,某些特殊运算符还具有功能支持。

我对设计进行了一些思考,但没有遇到任何问题。它效果很好,从概念上讲也很干净。

由于历史原因,常见的Lisp就是这样。

这里是系统的简要概述:

  • 为带有X的符号defmacro定义了全局宏时,符号X不会变为fboundp。相反,变成fboundp的是复合函数名称(macro X)

  • 然后(macro X)symbol-function和其他情况下都知道名称trace(symbol-function '(macro X))检索采用形式和环境的两参数扩展器功能。

  • 可以使用(defun (macro X) (form env) ...)编写宏。

  • 没有编译器宏;常规宏完成了编译器宏的工作。

  • 常规宏可以返回未扩展形式,以表示它正在拒绝扩展。如果词汇macrolet拒绝扩展,则机会将转到词汇更外部的macrolet,依此类推,直到全局defmacro。如果全局defmacro拒绝扩展,则该形式被视为扩展,因此必然是函数调用或特殊形式。

  • 如果我们同时拥有一个称为X的函数和宏,则可以使用(call (fun X) ...)(call 'X ...)调用函数定义,或者使用Lisp-1-style { {1}}评估程序dwim几乎总是通过其(dwim X ...)语法糖作为[]使用。

  • 出于某种完整性,提供了功能[X ...]mboundpmmakunbound,它们是symbol-macrofboundp的宏类似物和fmakunbound

  • 特殊运算符symbol-functionorand和其他一些运算符也具有函数定义,因此可以使用类似if的代码。

示例:对[mapcar or '(nil 2 t) '(1 0 3)] -> (1 2 t)进行固定折叠:

sqrt

但是,没有通过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) 的宏定义来实现(set (second x) 42)。那不会很好。主要原因是这将是太多负担。对于给定的功能,程序员可能希望拥有一个与实现分配语义无关的宏定义!

此外,如果second实现了场所语义,那么当它不嵌入在赋值操作中时会发生什么,从而根本不需要语义?基本上,要满足所有要求,就需要制定一种方案来编写宏,该宏的复杂性将等于或超过用于处理位置的现有逻辑。

实际上,TXR Lisp具有一种称为“位置宏”的特殊宏。表单仅用作场所时,才被识别为场所宏调用。但是,位置宏本身并不实现位置语义。他们只是简单地重写。位置宏必须向下扩展为可识别为位置的形式。

示例:指定(second x)在用作场所时的行为类似于(foo x)

(car 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) 扩展到某个地方以外的地方,则失败:

foo