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

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


当前回答

想想在C或c++中可以用宏和模板做什么。它们是管理重复代码的非常有用的工具,但它们在相当严重的方面受到限制。

有限的宏/模板语法限制了它们的使用。例如,不能编写扩展为类或函数以外内容的模板。宏和模板不容易维护内部数据。 C和c++复杂且不规则的语法使得编写非常通用的宏非常困难。

Lisp和Lisp宏解决了这些问题。

Lisp宏是用Lisp编写的。您拥有Lisp的全部功能来编写宏。 Lisp有一个非常规则的语法。

与任何精通c++的人交谈,问他们花了多长时间来学习模板元编程所需的所有模板。或者是《现代c++设计》等(优秀)书籍中的所有疯狂技巧,尽管语言已经标准化了10年,但这些技巧仍然很难调试,而且(在实践中)无法在真实的编译器之间移植。如果用于元编程的语言与用于编程的语言相同,那么所有这些问题都消失了!

其他回答

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();
    }

通用Lisp宏本质上扩展了代码的“语法原语”。

例如,在C语言中,switch/case结构只适用于整型,如果你想将它用于浮点数或字符串,你就只能使用嵌套的if语句和显式比较。你也不可能编写一个C宏来为你做这项工作。

但是,由于lisp宏(本质上)是一个lisp程序,它接受代码片段作为输入,并返回代码来替换宏的“调用”,因此您可以尽可能地扩展您的“原语”库,通常最终会得到一个更可读的程序。

要在C中做同样的事情,您必须编写一个自定义预处理器,它会吃掉您的初始(不完全是C)源代码,并吐出C编译器可以理解的东西。这不是一种错误的方法,但它不一定是最简单的。

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

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

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

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

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

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

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

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

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

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宏。

希望这能有所帮助。