特殊形式和宏之间的实际区别是什么?

20

特殊形式和宏之间有什么实际区别?它们的不同之处在哪里?


1
可能是重复的问题:在宏和内置函数之间安全地忽略差异吗? - amalloy
5个回答

20

这些术语并不完全同义,但它们也不是互斥的(假定本答案中的 Scheme):

  • 特殊形式(在 Scheme 报告中也称为 语法)是一个不按照函数应用的默认规则进行计算的表达式。(默认规则是先eval所有子表达式,然后将第一个子表达式的结果应用到其他子表达式的结果列表上。)
  • 宏系统 是一种语言功能,允许在语言本身内部定义新的特殊形式。一个是使用宏系统定义的特殊形式。

因此可以说,“特殊形式”是与接口或语义有关的术语,而“宏”是与实现有关的术语。“特殊形式”意味着“这些表达式使用特殊规则进行计算”,而“宏”则意味着“这是一种计算某些表达式的特殊规则的实现方法。”

现在一个重要的事情是,大多数 Scheme 特殊形式都可以从一个非常小的原始操作集合定义为宏:lambdaif 和宏。仅提供这些操作的最小 Scheme 实现仍然可以将其余部分实现为宏。最近的 Scheme 报告通过称这些特殊形式为“库语法”,并且可以用宏来定义以此作出了区分。但是,实际上,实用的 Scheme 系统通常实现了更丰富的表单作为原语。

从语义上讲,关于一个表达式的唯一重要的事情就是使用什么规则来计算它,而不是如何实现这个规则。因此,在这个意义上,特殊形式是作为宏还是作为原语实现并不重要。但另一方面,Scheme 系统的实现细节通常会泄漏,所以您可能会关心它……


1
你也可以在 lambda 的基础上实现 if 宏。这并不是非常高效,但了解这一点还是很不错的。 - amalloy
我认为这个答案是最明确的。因为它根据语义来定义 special-form,而不依赖于具体实现细节。 - Pedro Rolo
1
@amalloy,我对如何实现这个很感兴趣,你有相关链接吗? - Simon Lindgren
3
@SimonLindgren 我可能在这里夸张了,因为我认为你需要重构很多语言才能更多地使用lambda。但是http://codon.com/programming-with-nothing是一篇关于Ruby中lambda演算的迷人博客文章,https://www.refheap.com/paste/5073/fullscreen是Clojure的翻译。从那里,你可以定义一个宏,让你“实际”只使用lambda,并仍然拥有漂亮的`if`作为语法糖。 - amalloy

11

Lisp有一些语言原语,它们构成了Lisp形式:

  • 文字数据: 数字、字符串、结构体等
  • 函数调用,例如(sin 2.1)或者((lambda (a b) (+ a b 2)) 3 4)
  • 特殊操作符用于特殊形式。这些是基本的内置语言元素。请参见Common Lisp中的Special Operators。需要在解释器和编译器中实现这些操作符。Common Lisp没有提供引入新特殊操作符或提供自己版本的方法。代码解析工具需要理解这些特殊操作符;在Lisp社区中,这些工具通常称为“代码漫步器”。在定义Common Lisp标准期间,确保了特殊操作符数量非常小,所有扩展都是通过新函数和新宏完成的。
  • :宏是将源代码转换的函数。递归地进行转换,直到源代码中没有宏为止。Common Lisp具有内置的宏,并允许用户编写新的宏。

因此,特殊形式和宏之间最重要的实际区别在于:特殊操作符是内置的语法和语义,开发人员无法编写。宏可以由开发人员编写。


4
与特殊形式不同,宏表单可以进行宏展开:
CL-USER(1): (macroexpand '(with-slots (x y z)
                              foo
                            (format t "~&X = ~A" x)))

(LET ((#:G925 FOO))
  (DECLARE (IGNORABLE #:G925))
  (DECLARE (SB-PCL::%VARIABLE-REBINDING #:G925 FOO))
  #:G925
  (SYMBOL-MACROLET ((X (SLOT-VALUE #:G925 'X))
                    (Y (SLOT-VALUE #:G925 'Y))
                    (Z (SLOT-VALUE #:G925 'Z)))
    (FORMAT T "~&X = ~A" X)))
T

嗨,马蒂亚斯。在《tAoAI》中,Norvig声称setf是一个特殊形式。然而,它可以进行宏展开。例如:(macroexpand '(setf test 4)) 展开为 (SETQ TEST 4)。也许书中有错误?我在这里也没有看到setf:http://www.lispworks.com/documentation/lw60/CLHS/Body/03_ababa.htm#clspecialops - anonymous

4

对我来说,最实用的区别在于调试器:宏不会出现在调试器中;相反,通常是宏展开后的晦涩代码出现在调试器中。调试这样的代码真的很麻烦,这也是确保宏坚如磐石之前要开始依赖它们的一个很好的理由。


1
如果您没有宏调试器,这可能是一个问题,但实际上一些Lisp确实有。例如,Racket有一个您甚至可以从REPL使用的调试器。 - Asumu Takikawa

2

懒人的超级简短回答:

您可以随时编写自己的宏,但是如果要添加特殊形式,则需要重新编译Clojure。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接