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

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


当前回答

一行程序回答:

简单的语法=>宏over表达式=>简洁=>抽象=>强大


Lisp宏只是以编程方式编写代码。也就是说,在展开宏之后,您得到的只不过是没有宏的Lisp代码。所以,原则上来说,他们没有什么新成就。

然而,它们与其他编程语言中的宏不同,它们在表达式级别上编写代码,而其他宏在字符串级别上编写代码。这在lisp中是独一无二的,这要感谢他们的括号;或者更准确地说,它们的最小语法多亏了括号。

正如本文中的许多示例所示,以及Paul Graham的On Lisp, Lisp宏可以成为使代码更加简洁的工具。当简洁性达到一定程度时,它为代码提供了新的抽象级别,使其更加简洁。回到第一点,原则上他们没有提供任何新的东西,但这就像说既然纸和铅笔(几乎)组成了图灵机,我们就不需要真正的计算机。

如果你懂点数学,想想为什么函子和自然变换是有用的想法。原则上,它们没有提供任何新东西。然而,通过将它们扩展到较低层次的数学中,你会发现几个简单想法的组合(就范畴理论而言)可能需要10页才能写下来。你喜欢哪一种?

其他回答

一行程序回答:

简单的语法=>宏over表达式=>简洁=>抽象=>强大


Lisp宏只是以编程方式编写代码。也就是说,在展开宏之后,您得到的只不过是没有宏的Lisp代码。所以,原则上来说,他们没有什么新成就。

然而,它们与其他编程语言中的宏不同,它们在表达式级别上编写代码,而其他宏在字符串级别上编写代码。这在lisp中是独一无二的,这要感谢他们的括号;或者更准确地说,它们的最小语法多亏了括号。

正如本文中的许多示例所示,以及Paul Graham的On Lisp, Lisp宏可以成为使代码更加简洁的工具。当简洁性达到一定程度时,它为代码提供了新的抽象级别,使其更加简洁。回到第一点,原则上他们没有提供任何新的东西,但这就像说既然纸和铅笔(几乎)组成了图灵机,我们就不需要真正的计算机。

如果你懂点数学,想想为什么函子和自然变换是有用的想法。原则上,它们没有提供任何新东西。然而,通过将它们扩展到较低层次的数学中,你会发现几个简单想法的组合(就范畴理论而言)可能需要10页才能写下来。你喜欢哪一种?

While the above all explains what macros are and even have cool examples, I think the key difference between a macro and a normal function is that LISP evaluates all the parameters first before calling the function. With a macro it's the reverse, LISP passes the parameters unevaluated to the macro. For example, if you pass (+ 1 2) to a function, the function will receive the value 3. If you pass this to a macro, it will receive a List( + 1 2). This can be used to do all kinds of incredibly useful stuff.

Adding a new control structure, e.g. loop or the deconstruction of a list Measure the time it takes to execute a function passed in. With a function the parameter would be evaluated before control is passed to the function. With the macro, you can splice your code between the start and stop of your stopwatch. The below has the exact same code in a macro and a function and the output is very different. Note: This is a contrived example and the implementation was chosen so that it is identical to better highlight the difference. (defmacro working-timer (b) (let ( (start (get-universal-time)) (result (eval b))) ;; not splicing here to keep stuff simple ((- (get-universal-time) start)))) (defun my-broken-timer (b) (let ( (start (get-universal-time)) (result (eval b))) ;; doesn't even need eval ((- (get-universal-time) start)))) (working-timer (sleep 10)) => 10 (broken-timer (sleep 10)) => 0

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

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

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

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

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

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

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

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

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

我从通用的lisp烹饪书中得到了这个,我认为它解释了为什么lisp宏是有用的。

宏是一段普通的Lisp代码,它对另一段假定的Lisp代码进行操作,将其翻译成(更接近于)可执行的Lisp。这听起来可能有点复杂,所以让我们举一个简单的例子。假设您想要一个版本的setq,将两个变量设置为相同的值。所以如果你写

(setq2 x y (+ z 3))

当z=8时,x和y都被设为11。(我想不出这有什么用,但这只是一个例子。)

It should be obvious that we can't define setq2 as a function. If x=50 and y=-5, this function would receive the values 50, -5, and 11; it would have no knowledge of what variables were supposed to be set. What we really want to say is, When you (the Lisp system) see (setq2 v1 v2 e), treat it as equivalent to (progn (setq v1 e) (setq v2 e)). Actually, this isn't quite right, but it will do for now. A macro allows us to do precisely this, by specifying a program for transforming the input pattern (setq2 v1 v2 e)" into the output pattern (progn ...)."

如果你觉得这很好,你可以继续读下去: http://cl-cookbook.sourceforge.net/macros.html