本文翻译自:What makes Lisp macros so special?
Reading Paul Graham's essays on programming languages one would think that Lisp macros are the only way to go. 阅读Paul Graham关于编程语言的论文 ,人们会认为Lisp宏是唯一可行的方法。 As a busy developer, working on other platforms, I have not had the privilege of using Lisp macros. 作为一个忙于开发人员,在其他平台上工作,我没有使用Lisp宏的特权。 As someone who wants to understand the buzz, please explain what makes this feature so powerful. 作为想要了解嗡嗡声的人,请解释是什么让这个功能如此强大。
Please also relate this to something I would understand from the worlds of Python, Java, C# or C development. 还请将此与我从Python,Java,C#或C开发世界中理解的内容联系起来。
#1楼
参考:https://stackoom.com/question/17Gm/是什么让Lisp宏如此特别
#2楼
I got this from The common lisp cookbook but I think it explained why lisp macros are good in a nice way. 我从普通的lisp食谱中得到了这个,但我认为它解释了为什么lisp宏好的方式。
"A macro is an ordinary piece of Lisp code that operates on another piece of putative Lisp code, translating it into (a version closer to) executable Lisp. That may sound a bit complicated, so let's give a simple example. Suppose you want a version of setq that sets two variables to the same value. So if you write “宏是一段普通的Lisp代码,它运行在另一个推定的Lisp代码上,将其转换为(更接近于可执行Lisp的版本)。这可能听起来有点复杂,所以让我们举一个简单的例子。假设你想要一个setq的两个版本将两个变量设置为相同的值。所以如果你写的话
(setq2 x y (+ z 3))
when z=8
both x and y are set to 11. (I can't think of any use for this, but it's just an example.) 当z=8
,x和y都设置为11.(我想不出任何用途,但这只是一个例子。)
It should be obvious that we can't define setq2 as a function. 很明显,我们不能将setq2定义为函数。 If x=50
and y=-5
, this function would receive the values 50, -5, and 11; 如果x=50
且y=-5
,则该函数将接收值50,-5和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))
. 我们真正想说的是,当你(Lisp系统)看到(setq2 v1 v2 e)
,将它视为等同于(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 ...)
." 宏允许我们通过指定用于将输入模式(setq2 v1 v2 e)
转换为输出模式(progn ...)
。
If you thought this was nice you can keep on reading here: http://cl-cookbook.sourceforge.net/macros.html 如果您认为这很好,您可以继续阅读: http : //cl-cookbook.sourceforge.net/macros.html
#3楼
Since the existing answers give good concrete examples explaining what macros achieve and how, perhaps it'd help to collect together some of the thoughts on why the macro facility is a significant gain in relation to other languages ; 既然现有的答案提供了很好的具体例子来解释宏实现了什么以及如何实现,也许它有助于汇集一些关于为什么宏观设施相对于其他语言获得显着收益的想法; first from these answers, then a great one from elsewhere: 首先来自这些答案,然后是来自其他地方的伟大答案:
... in C, you would have to write a custom pre-processor [which would probably qualify as a sufficiently complicated C program ] ... ...在C中,你必须编写一个自定义的预处理器[这可能有资格作为一个足够复杂的C程序 ] ......
Talk to anyone that's mastered C++ and ask them how long they spent learning all the template fudgery they need to do template metaprogramming [which is still not as powerful]. 与任何掌握了C ++的人交谈,并询问他们花了多长时间学习模板元编程所需的模板所有模板[这仍然没有那么强大]。
— Matt Curtis - 马特柯蒂斯
... in Java you have to hack your way with bytecode weaving, although some frameworks like AspectJ allows you to do this using a different approach, it's fundamentally a hack. ...在Java中你必须使用字节码编织来破解你的方式,尽管像AspectJ这样的一些框架允许你使用不同的方法来实现这一点,但它从根本上说是一个黑客攻击。
— Miguel Ping - 米格尔·平
DOLIST is similar to Perl's foreach or Python's for. DOLIST类似于Perl的foreach或Python的。 Java added a similar kind of loop construct with the "enhanced" for loop in Java 1.5, as part of JSR-201. 作为JSR-201的一部分,Java在Java 1.5中添加了类似的循环结构和“增强”for循环。 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. 一个Lisp程序员在他们的代码中注意到一个共同的模式,可以编写一个宏来给自己提供该模式的源级抽象。 A Java programmer who notices the same pattern has to convince Sun that this particular abstraction is worth adding to the language. 注意到相同模式的Java程序员必须说服Sun,这种特殊的抽象值得添加到该语言中。 Then Sun has to publish a JSR and convene an industry-wide "expert group" to hash everything out. 然后Sun必须发布一个JSR并召集一个行业范围的“专家组”来解决所有问题。 That process--according to Sun--takes an average of 18 months. 根据Sun的说法,这个过程平均需要18个月。 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. 甚至一旦Java程序员最喜欢的编译器支持新版本的Java,他们可能“仍然”不能使用新功能,直到它们被允许破坏与旧版Java的源兼容性。 So an annoyance that Common Lisp programmers can resolve for themselves within five minutes plagues Java programmers for years. 因此,Common Lisp程序员可以在五分钟内自行解决的烦恼困扰着Java程序员多年。
— Peter Seibel, in "Practical Common Lisp" - Peter Seibel,“Practical Common Lisp”
#4楼
You will find a comprehensive debate around lisp macro here . 你会在这里找到关于lisp宏的全面辩论。
An interesting subset of that article: 该文章的一个有趣的子集:
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是不同的。 Lisp macros do have access to the parser, and it is a really simple parser. Lisp的宏做访问解析器,这是一个非常简单的解析器。 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; Lisp宏不是一个字符串,而是一个列表形式的预处理源代码,因为Lisp程序的源不是字符串; it is a list. 这是一个清单。 And Lisp programs are really good at taking apart lists and putting them back together. Lisp程序非常擅长拆分列表并将它们重新组合在一起。 They do this reliably, every day. 他们每天都可靠地做到这一点。
Here is an extended example. 这是一个扩展的例子。 Lisp has a macro, called "setf", that performs assignment. Lisp有一个名为“setf”的宏,它执行赋值。 The simplest form of setf is 最简单的setf形式是
(setf x whatever)
which sets the value of the symbol "x" to the value of the expression "whatever". 它将符号“x”的值设置为表达式“whatever”的值。
Lisp also has lists; Lisp也有名单; you can use the "car" and "cdr" functions to get the first element of a list or the rest of the list, respectively. 您可以使用“car”和“cdr”函数分别获取列表的第一个元素或列表的其余部分。
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". 这是“rplaca”。 But you do not have to remember "rplaca", because you can write 但你不必记住“rplaca”,因为你可以写
(setf (car somelist) whatever)
to set the car of somelist. 设置某人的车。
What is really happening here is that "setf" is a macro. 这里真正发生的是“setf”是一个宏。 At compile time, it examines its arguments, and it sees that the first one has the form (car SOMETHING). 在编译时,它检查它的参数,并且它看到第一个具有形式(汽车SOMETHING)。 It says to itself "Oh, the programmer is trying to set the car of somthing. The function to use for that is 'rplaca'." 它对自己说“哦,程序员正试图设置汽车的东西。用于此的功能是'rplaca'。” And it quietly rewrites the code in place to: 它悄悄地将代码重写为:
(rplaca somelist whatever)
#5楼
Lisp macros allow you to decide when (if at all) any part or expression will be evaluated. Lisp宏允许您决定何时(如果有的话)将评估任何部分或表达式。 To put a simple example, think of C's: 举一个简单的例子,想想C:
expr1 && expr2 && expr3 ...
What this says is: Evaluate expr1
, and, should it be true, evaluate expr2
, etc. 这就是:评估expr1
,如果它是真的,评估expr2
等。
Now try to make this &&
into a function... thats right, you can't. 现在尝试将此&&
变成一个函数......这是正确的,你不能。 Calling something like: 打电话给:
and(expr1, expr2, expr3)
Will evaluate all three exprs
before yielding an answer regardless of whether expr1
was false! 无论expr1
是否为假,都会在得到答案之前评估所有三个exprs
!
With lisp macros you can code something like: 使用lisp宏,您可以编写如下代码:
(defmacro && (expr1 &rest exprs)
`(if ,expr1 ;` Warning: I have not tested
(&& ,@exprs) ; this and might be wrong!
nil))
now you have an &&
, which you can call just like a function and it won't evaluate any forms you pass to it unless they are all true. 现在你有一个&&
,你可以像一个函数一样调用它,它不会评估你传递给它的任何形式,除非它们都是真的。
To see how this is useful, contrast: 对比,看看这是如何有用的:
(&& (very-cheap-operation)
(very-expensive-operation)
(operation-with-serious-side-effects))
and: 和:
and(very_cheap_operation(),
very_expensive_operation(),
operation_with_serious_side_effects());
Other things you can do with macros are creating new keywords and/or mini-languages (check out the (loop ...)
macro for an example), integrating other languages into lisp, for example, you could write a macro that lets you say something like: 你可以用宏做的其他事情是创建新的关键字和/或迷你语言(查看(loop ...)
宏的例子),将其他语言集成到lisp中,例如,你可以写一个宏,让你说出类似的话:
(setvar *rows* (sql select count(*)
from some-table
where column1 = "Yes"
and column2 like "some%string%")
And thats not even getting into Reader macros . 而这甚至没有进入Reader宏 。
Hope this helps. 希望这可以帮助。
#6楼
Common Lisp macros essentially extend the "syntactic primitives" of your code. Common Lisp宏本质上扩展了代码的“语法原语”。
For example, in C, the switch/case construct only works with integral types and if you want to use it for floats or strings, you are left with nested if statements and explicit comparisons. 例如,在C中,switch / case结构只适用于整数类型,如果你想将它用于浮点数或字符串,你可以使用嵌套的if语句和显式比较。 There's also no way you can write a C macro to do the job for you. 你也无法编写C宏来为你完成这项工作。
But, since a lisp macro is (essentially) a lisp program that takes snippets of code as input and returns code to replace the "invocation" of the macro, you can extend your "primitives" repertoire as far as you want, usually ending up with a more readable program. 但是,由于lisp宏(基本上)是一个lisp程序,它将代码片段作为输入并返回代码以替换宏的“调用”,因此您可以根据需要扩展“原语”指令集,通常最终有一个更易读的程序。
To do the same in C, you would have to write a custom pre-processor that eats your initial (not-quite-C) source and spits out something that a C compiler can understand. 要在C中执行相同的操作,您必须编写一个自定义预处理器,它会占用您的初始(非C)源并吐出C编译器可以理解的内容。 It's not a wrong way to go about it, but it's not necessarily the easiest. 这不是一个错误的方法,但它不一定是最容易的。