阅读Paul Graham关于编程语言的文章,你可能会认为Lisp宏是唯一的选择。作为一个忙碌的开发人员,在其他平台上工作,我还没有使用Lisp宏的特权。作为一个想要了解热门话题的人,请解释一下是什么让这个功能如此强大。

请将这一点与我从Python、Java、c#或C开发世界中理解的东西联系起来。


当前回答

由于现有的答案给出了很好的具体例子来解释宏实现了什么以及如何实现的,也许它会帮助收集一些关于为什么宏功能相对于其他语言是一个重要的收获的想法;首先是这些答案,然后是其他地方的一个很棒的答案:

... 在C语言中,你必须编写一个自定义的预处理器(这可能是一个足够复杂的C程序)……

—瓦廷

与任何精通c++的人交谈,问他们花了多长时间来学习模板元编程(仍然不是那么强大)所需要的所有模板。

马特·柯蒂斯

... 在Java中,你必须破解字节码编织的方法,尽管像AspectJ这样的框架允许你使用不同的方法来做到这一点,但它基本上是一种破解。

——米格尔·平

DOLIST is similar to Perl's foreach or Python's for. Java added a similar kind of loop construct with the "enhanced" for loop in Java 1.5, as part of JSR-201. Notice what a difference macros make. A Lisp programmer who notices a common pattern in their code can write a macro to give themselves a source-level abstraction of that pattern. A Java programmer who notices the same pattern has to convince Sun that this particular abstraction is worth adding to the language. Then Sun has to publish a JSR and convene an industry-wide "expert group" to hash everything out. That process--according to Sun--takes an average of 18 months. After that, the compiler writers all have to go upgrade their compilers to support the new feature. And even once the Java programmer's favorite compiler supports the new version of Java, they probably ''still'' can't use the new feature until they're allowed to break source compatibility with older versions of Java. So an annoyance that Common Lisp programmers can resolve for themselves within five minutes plagues Java programmers for years.

——peter Seibel,在《Practical Common Lisp》中

其他回答

我不确定我能给每个人的(优秀的)帖子添加一些见解,但是……

Lisp宏工作得很好,因为Lisp语法的本质。

Lisp是一种非常规则的语言(把所有东西都想象成一个列表);宏使您能够将数据和代码视为相同的(不需要字符串解析或其他技巧来修改lisp表达式)。将这两个特性结合起来,就有了一种非常简洁的方式来修改代码。

编辑:我想说的是Lisp是同构的,这意味着Lisp程序的数据结构是用Lisp本身编写的。

因此,您最终可以在语言之上创建自己的代码生成器,使用语言本身的所有功能(例如。在Java中,你必须破解字节码编织的方法,尽管一些框架(如AspectJ)允许你使用不同的方法来做到这一点,但它基本上是一种破解)。

在实践中,使用宏可以在lisp的基础上构建自己的迷你语言,而不需要学习其他语言或工具,并使用语言本身的全部功能。

您将在这里找到关于lisp宏的全面辩论。

这篇文章的一个有趣的子集:

In most programming languages, syntax is complex. Macros have to take apart program syntax, analyze it, and reassemble it. They do not have access to the program's parser, so they have to depend on heuristics and best-guesses. Sometimes their cut-rate analysis is wrong, and then they break. But Lisp is different. Lisp macros do have access to the parser, and it is a really simple parser. A Lisp macro is not handed a string, but a preparsed piece of source code in the form of a list, because the source of a Lisp program is not a string; it is a list. And Lisp programs are really good at taking apart lists and putting them back together. They do this reliably, every day. Here is an extended example. Lisp has a macro, called "setf", that performs assignment. The simplest form of setf is (setf x whatever) which sets the value of the symbol "x" to the value of the expression "whatever". Lisp also has lists; you can use the "car" and "cdr" functions to get the first element of a list or the rest of the list, respectively. Now what if you want to replace the first element of a list with a new value? There is a standard function for doing that, and incredibly, its name is even worse than "car". It is "rplaca". But you do not have to remember "rplaca", because you can write (setf (car somelist) whatever) to set the car of somelist. What is really happening here is that "setf" is a macro. At compile time, it examines its arguments, and it sees that the first one has the form (car SOMETHING). It says to itself "Oh, the programmer is trying to set the car of somthing. The function to use for that is 'rplaca'." And it quietly rewrites the code in place to: (rplaca somelist whatever)

Lisp宏代表了几乎在任何大型编程项目中都会出现的一种模式。最终,在一个大的程序中,你会有一段代码,你会意识到,如果你写一个程序,把源代码输出为文本,然后你就可以粘贴进去,这会更简单,更不容易出错。

在Python中,对象有两个方法__repr__和__str__。__str__只是人类可读的表示。__repr__返回一个有效的Python代码表示,也就是说,可以作为有效的Python输入解释器。通过这种方式,您可以创建一些小的Python代码片段,生成可以粘贴到实际源代码中的有效代码。

在Lisp中,整个过程由宏系统形式化。当然,它允许您为语法创建扩展并执行各种奇特的事情,但它的实际用处可以从上面总结出来。当然,Lisp宏系统允许您使用整个语言的全部功能来操作这些“片段”是有帮助的。

简而言之,宏是代码的转换。它们允许引入许多新的语法结构。例如,考虑c#中的LINQ。在lisp中,有类似的由宏实现的语言扩展(例如,内置循环构造,迭代)。宏显著地减少了代码重复。宏允许嵌入«小语言»(例如,在c#/java中可以使用xml进行配置,在lisp中可以使用宏实现同样的事情)。宏可能隐藏使用库的困难。

例如,在lisp中你可以写

(iter (for (id name) in-clsql-query "select id, name from users" on-database *users-database*)
      (format t "User with ID of ~A has name ~A.~%" id name))

这隐藏了所有数据库的东西(事务,正确的连接关闭,获取数据等),而在c#中,这需要创建SqlConnections, SqlCommands,将SqlParameters添加到SqlCommands,在SqlDataReaders上循环,正确地关闭它们。

lisp宏以程序片段作为输入。这个程序片段被表示为一个数据结构,可以按照您喜欢的任何方式进行操作和转换。最后,宏输出另一个程序片段,这个片段是在运行时执行的。

c#没有宏功能,但是如果编译器将代码解析为CodeDOM树,并将其传递给一个方法,该方法将其转换为另一个CodeDOM,然后将其编译为IL,则会有等效的宏功能。

这可以用来实现“糖”语法,如for each-statement using-clause, linq select-expressions等等,作为转换为底层代码的宏。

如果Java有宏,您就可以在Java中实现Linq语法,而不需要Sun更改基本语言。

下面是在c#中实现lisp风格的宏的伪代码:

define macro "using":
    using ($type $varname = $expression) $block
into:
    $type $varname;
    try {
       $varname = $expression;
       $block;
    } finally {
       $varname.Dispose();
    }