阅读Paul Graham关于编程语言的文章,你可能会认为Lisp宏是唯一的选择。作为一个忙碌的开发人员,在其他平台上工作,我还没有使用Lisp宏的特权。作为一个想要了解热门话题的人,请解释一下是什么让这个功能如此强大。
请将这一点与我从Python、Java、c#或C开发世界中理解的东西联系起来。
阅读Paul Graham关于编程语言的文章,你可能会认为Lisp宏是唯一的选择。作为一个忙碌的开发人员,在其他平台上工作,我还没有使用Lisp宏的特权。作为一个想要了解热门话题的人,请解释一下是什么让这个功能如此强大。
请将这一点与我从Python、Java、c#或C开发世界中理解的东西联系起来。
当前回答
想想在C或c++中可以用宏和模板做什么。它们是管理重复代码的非常有用的工具,但它们在相当严重的方面受到限制。
有限的宏/模板语法限制了它们的使用。例如,不能编写扩展为类或函数以外内容的模板。宏和模板不容易维护内部数据。 C和c++复杂且不规则的语法使得编写非常通用的宏非常困难。
Lisp和Lisp宏解决了这些问题。
Lisp宏是用Lisp编写的。您拥有Lisp的全部功能来编写宏。 Lisp有一个非常规则的语法。
与任何精通c++的人交谈,问他们花了多长时间来学习模板元编程所需的所有模板。或者是《现代c++设计》等(优秀)书籍中的所有疯狂技巧,尽管语言已经标准化了10年,但这些技巧仍然很难调试,而且(在实践中)无法在真实的编译器之间移植。如果用于元编程的语言与用于编程的语言相同,那么所有这些问题都消失了!
其他回答
一行程序回答:
简单的语法=>宏over表达式=>简洁=>抽象=>强大
Lisp宏只是以编程方式编写代码。也就是说,在展开宏之后,您得到的只不过是没有宏的Lisp代码。所以,原则上来说,他们没有什么新成就。
然而,它们与其他编程语言中的宏不同,它们在表达式级别上编写代码,而其他宏在字符串级别上编写代码。这在lisp中是独一无二的,这要感谢他们的括号;或者更准确地说,它们的最小语法多亏了括号。
正如本文中的许多示例所示,以及Paul Graham的On Lisp, Lisp宏可以成为使代码更加简洁的工具。当简洁性达到一定程度时,它为代码提供了新的抽象级别,使其更加简洁。回到第一点,原则上他们没有提供任何新的东西,但这就像说既然纸和铅笔(几乎)组成了图灵机,我们就不需要真正的计算机。
如果你懂点数学,想想为什么函子和自然变换是有用的想法。原则上,它们没有提供任何新东西。然而,通过将它们扩展到较低层次的数学中,你会发现几个简单想法的组合(就范畴理论而言)可能需要10页才能写下来。你喜欢哪一种?
Lisp宏允许您决定何时(如果有的话)对任何部分或表达式求值。举个简单的例子,想想C语言:
expr1 && expr2 && expr3 ...
它说的是:计算expr1,并且,如果它是正确的,计算expr2,等等。
现在试着把这个&&变成一个函数…没错,你不能。像这样调用:
and(expr1, expr2, expr3)
将在产生答案之前评估所有三个表达式,而不管expr1是否为假!
使用lisp宏,你可以编写如下代码:
(defmacro && (expr1 &rest exprs)
`(if ,expr1 ;` Warning: I have not tested
(&& ,@exprs) ; this and might be wrong!
nil))
现在你有一个&&,你可以像函数一样调用它,它不会计算你传递给它的任何表单,除非它们都为真。
要了解这是如何有用的,请进行对比:
(&& (very-cheap-operation)
(very-expensive-operation)
(operation-with-serious-side-effects))
and:
and(very_cheap_operation(),
very_expensive_operation(),
operation_with_serious_side_effects());
你可以用宏做的其他事情是创建新的关键字和/或迷你语言(例如,查看(loop…)宏),将其他语言集成到lisp中,例如,你可以编写一个宏,让你这样说:
(setvar *rows* (sql select count(*)
from some-table
where column1 = "Yes"
and column2 like "some%string%")
这还没有涉及到Reader宏。
希望这能有所帮助。
由于现有的答案给出了很好的具体例子来解释宏实现了什么以及如何实现的,也许它会帮助收集一些关于为什么宏功能相对于其他语言是一个重要的收获的想法;首先是这些答案,然后是其他地方的一个很棒的答案:
... 在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》中
简而言之,宏是代码的转换。它们允许引入许多新的语法结构。例如,考虑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();
}