Reading Paul Graham's essays on programming languages one would think that Lisp macros are the only way to go. As a busy developer, working on other platforms, I have not had the privilege of using Lisp macros. As someone who wants to understand the buzz, please explain what makes this feature so powerful.
阅读Paul Graham关于编程语言的论文,人们会认为Lisp宏是唯一可行的方法。作为一个忙于开发人员,在其他平台上工作,我没有使用Lisp宏的特权。作为想要了解嗡嗡声的人,请解释是什么让这个功能如此强大。
Please also relate this to something I would understand from the worlds of Python, Java, C# or C development.
还请将此与我从Python,Java,C#或C开发世界中理解的内容联系起来。
14 个解决方案
#1
269
To give the short answer, macros are used for defining language syntax extensions to Common Lisp or Domain Specific Languages (DSLs). These languages are embedded right into the existing Lisp code. Now, the DSLs can have syntax similar to Lisp (like Peter Norvig's Prolog Interpreter for Common Lisp) or completely different (e.g. Infix Notation Math for Clojure).
简而言之,宏用于定义Common Lisp或Domain Specific Languages(DSL)的语言语法扩展。这些语言嵌入到现有的Lisp代码中。现在,DSL可以具有类似于Lisp的语法(如Peter Norvig的Prolog Interpreter for Common Lisp)或完全不同(例如,用于Clojure的Infix表示法数学)。
Here is a more concrete example:
Python has list comprehensions built into the language. This gives a simple syntax for a common case. The line
这是一个更具体的例子:Python具有内置于该语言中的列表推导。这为常见案例提供了简单的语法。这条线
divisibleByTwo = [x for x in range(10) if x % 2 == 0]
yields a list containing all even numbers between 0 and 9. Back in the Python 1.5 days there was no such syntax; you'd use something more like this:
产生一个包含0到9之间所有偶数的列表。回到Python 1.5天,没有这样的语法;你会使用更像这样的东西:
divisibleByTwo = []
for x in range( 10 ):
if x % 2 == 0:
divisibleByTwo.append( x )
These are both functionally equivalent. Let's invoke our suspension of disbelief and pretend Lisp has a very limited loop macro that just does iteration and no easy way to do the equivalent of list comprehensions.
这些都是功能相同的。让我们暂停怀疑,并假装Lisp有一个非常有限的循环宏,它只是迭代而没有简单的方法来完成相当于列表推导。
In Lisp you could write the following. I should note this contrived example is picked to be identical to the Python code not a good example of Lisp code.
在Lisp中,您可以编写以下内容。我应该注意这个人为的例子与Python代码相同,而不是Lisp代码的一个很好的例子。
;; the following two functions just make equivalent of Python's range function
;; you can safely ignore them unless you are running this code
(defun range-helper (x)
(if (= x 0)
(list x)
(cons x (range-helper (- x 1)))))
(defun range (x)
(reverse (range-helper (- x 1))))
;; equivalent to the python example:
;; define a variable
(defvar divisibleByTwo nil)
;; loop from 0 upto and including 9
(loop for x in (range 10)
;; test for divisibility by two
if (= (mod x 2) 0)
;; append to the list
do (setq divisibleByTwo (append divisibleByTwo (list x))))
Before I go further, I should better explain what a macro is. It is a transformation performed on code by code. That is, a piece of code, read by the interpreter (or compiler), which takes in code as an argument, manipulates and the returns the result, which is then run in-place.
在我走得更远之前,我应该更好地解释宏是什么。它是按代码执行的转换。也就是说,由解释器(或编译器)读取的一段代码,它接受代码作为参数,操作并返回结果,然后就地运行。
Of course that's a lot of typing and programmers are lazy. So we could define DSL for doing list comprehensions. In fact, we're using one macro already (the loop macro).
当然,很多打字和程序员都很懒惰。所以我们可以定义DSL来做列表推导。实际上,我们已经使用了一个宏(循环宏)。
Lisp defines a couple of special syntax forms. The quote ('
) indicates the next token is a literal. The quasiquote or backtick (`
) indicates the next token is a literal with escapes. Escapes are indicated by the comma operator. The literal '(1 2 3)
is the equivalent of Python's [1, 2, 3]
. You can assign it to another variable or use it in place. You can think of `(1 2 ,x)
as the equivalent of Python's [1, 2, x]
where x
is a variable previously defined. This list notation is part of the magic that goes into macros. The second part is the Lisp reader which intelligently substitutes macros for code but that is best illustrated below:
Lisp定义了几种特殊的语法形式。引号(')表示下一个标记是文字。 quasiquote或反引号(`)表示下一个标记是带有转义的文字。转义由逗号运算符指示。文字'(1 2 3)相当于Python的[1,2,3]。您可以将其分配给另一个变量或在适当的位置使用它。您可以将`(1 2,x)视为Python的[1,2,x]的等价物,其中x是先前定义的变量。这个列表符号是进入宏的魔法的一部分。第二部分是Lisp阅读器,它智能地将宏替换为代码,但最好说明如下:
So we can define a macro called lcomp
(short for list comprehension). It's syntax will be exactly like the python that we used in the example [x for x in range(10) if x % 2 == 0]
- (lcomp x for x in (range 10) if (= (% x 2) 0))
所以我们可以定义一个名为lcomp的宏(列表推导的简称)。它的语法将与我们在示例中使用的python完全相同[x,如果x%2 == 0则x在范围(10)中 - (lcomp x表示x in(范围10)if(=(%x 2)) 0))
(defmacro lcomp (expression for var in list conditional conditional-test)
;; create a unique variable name for the result
(let ((result (gensym)))
;; the arguments are really code so we can substitute them
;; store nil in the unique variable name generated above
`(let ((,result nil))
;; var is a variable name
;; list is the list literal we are suppose to iterate over
(loop for ,var in ,list
;; conditional is if or unless
;; conditional-test is (= (mod x 2) 0) in our examples
,conditional ,conditional-test
;; and this is the action from the earlier lisp example
;; result = result + [x] in python
do (setq ,result (append ,result (list ,expression))))
;; return the result
,result)))
Now we can execute at the command line:
现在我们可以在命令行执行:
CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)
Pretty neat, huh? Now it doesn't stop there. You have a mechanism, or a paintbrush, if you like. You can have any syntax you could possibly want. Like Python or C#'s with
syntax. Or .NET's LINQ syntax. In end, this is what attracts people to Lisp - ultimate flexibility.
挺整洁的,对吧?现在它并不止于此。如果你愿意,你有一个机制或画笔。您可以拥有任何您可能想要的语法。像Python或C#的语法。或.NET的LINQ语法。最后,这是吸引人们使用Lisp的最佳灵活性。
#2
97
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 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.
但是Lisp是不同的。 Lisp宏可以访问解析器,它是一个非常简单的解析器。 Lisp宏不是一个字符串,而是一个列表形式的预处理源代码,因为Lisp程序的源不是字符串;这是一个清单。 Lisp程序非常善于拆分列表并将它们重新组合在一起。他们每天都可靠地做到这一点。
Here is an extended example. Lisp has a macro, called "setf", that performs assignment. The simplest form of setf is
这是一个扩展的例子。 Lisp有一个名为“setf”的宏,它执行赋值。最简单的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; you can use the "car" and "cdr" functions to get the first element of a list or the rest of the list, respectively.
Lisp也有名单;您可以使用“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". But you do not have to remember "rplaca", because you can write
现在,如果您想用新值替换列表的第一个元素,该怎么办?这样做有一个标准功能,令人难以置信的是,它的名字甚至比“汽车”更糟糕。这是“rplaca”。但你不必记住“rplaca”,因为你可以写
(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:
这里真正发生的是“setf”是一个宏。在编译时,它检查它的参数,并且它看到第一个具有形式(汽车SOMETHING)。它对自己说“哦,程序员正试图设置汽车的东西。用于它的功能是'rplaca'。”它悄悄地将代码重写为:
(rplaca somelist whatever)
#3
51
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. There's also no way you can write a C macro to do the job for you.
例如,在C中,switch / case结构只适用于整数类型,如果你想将它用于浮点数或字符串,你可以使用嵌套的if语句和显式比较。你也无法编写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. It's not a wrong way to go about it, but it's not necessarily the easiest.
要在C中执行相同操作,您必须编写一个自定义预处理器,它会占用您的初始(非常C)源并吐出C编译器可以理解的内容。这不是一个错误的方法,但它不一定是最容易的。
#4
38
Lisp macros allow you to decide when (if at all) any part or expression will be evaluated. To put a simple example, think of C's:
Lisp宏允许您决定何时(如果有的话)将评估任何部分或表达式。举一个简单的例子,想想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:
你可以用宏做的其他事情是创建新的关键字和/或迷你语言(查看(循环...)宏的例子),将其他语言集成到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.
希望这可以帮助。
#5
28
I don't think I've ever seen Lisp macros explained better than by this fellow: http://www.defmacro.org/ramblings/lisp.html
我认为我从未见过Lisp宏解释得比这个人更好:http://www.defmacro.org/ramblings/lisp.html
#6
10
A lisp macro takes a program fragment as input. This program fragment is represented a data structure which can be manipulated and transformed any way you like. In the end the macro outputs another program fragment, and this fragment is what is executed at runtime.
lisp宏将程序片段作为输入。该程序片段表示一种数据结构,可以按照您喜欢的方式进行操作和转换。最后,宏输出另一个程序片段,这个片段是在运行时执行的。
C# does not have a macro facility, however an equivalent would be if the compiler parsed the code into a CodeDOM-tree, and passed that to a method, which transformed this into another CodeDOM, which is then compiled into IL.
C#没有宏工具,但是如果编译器将代码解析为CodeDOM树,并将其传递给方法,则将其转换为另一个CodeDOM,然后将其编译为IL。
This could be used to implement "sugar" syntax like the for each
-statement using
-clause, linq select
-expressions and so on, as macros that transforms into the underlying code.
这可以用于实现“糖”语法,如for-statement using-clause,linq select-expressions等,作为转换为底层代码的宏。
If Java had macros, you could implement Linq syntax in Java, without needing Sun to change the base language.
如果Java有宏,您可以在Java中实现Linq语法,而无需Sun更改基本语言。
Here is pseudo-code for how a lisp-style macro in C# for implementing using
could look:
下面是C#中实现使用的lisp样式宏如何看起来的伪代码:
define macro "using":
using ($type $varname = $expression) $block
into:
$type $varname;
try {
$varname = $expression;
$block;
} finally {
$varname.Dispose();
}
#7
9
Think of what you can do in C or C++ with macros and templates. They're very useful tools for managing repetitive code, but they're limited in quite severe ways.
想想你可以用C和C ++用宏和模板做什么。它们是管理重复代码的非常有用的工具,但它们受到严格限制。
- Limited macro/template syntax restricts their use. For example, you can't write a template which expands to something other than a class or a function. Macros and templates can't easily maintain internal data.
- The complex, very irregular syntax of C and C++ makes it difficult to write very general macros.
有限的宏/模板语法限制了它们的使用。例如,您不能编写扩展为类或函数以外的模板的模板。宏和模板无法轻松维护内部数据。
C和C ++的复杂,非常不规则的语法使得编写非常通用的宏变得困难。
Lisp and Lisp macros solve these problems.
Lisp和Lisp宏解决了这些问题。
- Lisp macros are written in Lisp. You have the full power of Lisp to write the macro.
- Lisp has a very regular syntax.
Lisp宏是用Lisp编写的。你有Lisp的全部功能来编写宏。
Lisp有一个非常规则的语法。
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. Or all the crazy tricks in (excellent) books like Modern C++ Design, which are still tough to debug and (in practice) non-portable between real-world compilers even though the language has been standardised for a decade. All of that melts away if the langauge you use for metaprogramming is the same language you use for programming!
与任何掌握了C ++的人交谈,并询问他们花了多长时间学习模板元编程所需的模板技巧。或者像(现代C ++设计)这些(优秀)书籍中的所有疯狂技巧,即使语言已经标准化了十年,仍然难以调试和(实际上)在现实编译器之间不可移植。如果用于元编程的语言与用于编程的语言相同,那么所有这些都会消失!
#8
8
I'm not sure I can add some insight to everyone's (excellent) posts, but...
我不确定我是否可以为每个人的(优秀)帖子添加一些见解,但......
Lisp macros work great because of the Lisp syntax nature.
由于Lisp语法本质,Lisp宏工作得很好。
Lisp is an extremely regular language (think of everything is a list); macros enables you to treat data and code as the same (no string parsing or other hacks are needed to modify lisp expressions). You combine these two features and you have a very clean way to modify code.
Lisp是一种非常规则的语言(想想一切都是一个列表);宏使您可以将数据和代码视为相同(不需要字符串解析或其他修改来修改lisp表达式)。您将这两个功能结合起来,并且有一种非常简洁的方法来修改代码。
Edit: What I was trying to say is that Lisp is homoiconic, which means that the data structure for a lisp program is written in lisp itself.
编辑:我想说的是Lisp是homoiconic,这意味着lisp程序的数据结构是用lisp本身编写的。
So, you end up with a way of creating your own code generator on top of the language using the language itself with all its power (eg. 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这样的某些框架允许您使用不同的方法做到这一点,它从根本上说是一个黑客攻击)。
In practice, with macros you end up building your own mini-language on top of lisp, without the need to learn additional languages or tooling, and with using the full power of the language itself.
在实践中,使用宏,您最终可以在lisp之上构建自己的迷你语言,而无需学习其他语言或工具,并且可以充分利用语言本身的强大功能。
#9
8
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 ++的人交谈,并问他们花了多长时间学习模板元编程所需的所有模板技巧[仍然没有那么强大]。
... 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这样的一些框架允许你使用不同的方法来实现这一点,但它从根本上说是一个黑客攻击。
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.
DOLIST类似于Perl的foreach或Python的。作为JSR-201的一部分,Java在Java 1.5中添加了类似的循环结构和“增强”for循环。注意宏有什么区别。一个Lisp程序员在他们的代码中注意到一个共同的模式可以编写一个宏来给自己提供该模式的源级抽象。注意到相同模式的Java程序员必须说服Sun,这种特殊的抽象值得添加到该语言中。然后Sun必须发布一个JSR并召集一个行业范围的“专家组”来解决所有问题。根据Sun的说法,这个过程平均需要18个月。之后,编译器编写者都必须升级他们的编译器以支持新功能。甚至一旦Java程序员最喜欢的编译器支持新版本的Java,他们可能“仍然”不能使用新功能,直到它们被允许破坏与旧版Java的源兼容性。因此,Common Lisp程序员可以在五分钟内解决自己的麻烦,困扰Java程序员多年。
—Peter Seibel, in "Practical Common Lisp"
-Peter Seibel,在“Practical Common Lisp”中
#10
6
Lisp macros represents a pattern that occurs in almost any sizeable programming project. Eventually in a large program you have a certain section of code where you realize it would be simpler and less error prone for you to write a program that outputs source code as text which you can then just paste in.
Lisp宏代表几乎任何大型编程项目中出现的模式。最终在一个大型程序中你有一段代码,你会发现它会更简单,更容易出错,你可以编写一个程序,输出源代码作为文本然后你可以粘贴。
In Python objects have two methods __repr__
and __str__
. __str__
is simply the human readable representation. __repr__
returns a representation that is valid Python code, which is to say, something that can be entered into the interpreter as valid Python. This way you can create little snippets of Python that generate valid code that can be pasted into your actually source.
在Python中,对象有两个方法__repr__和__str__。 __str__只是人类可读的表示。 __repr__返回一个有效Python代码的表示,也就是说,可以作为有效的Python输入到解释器中的东西。通过这种方式,您可以创建一小段Python,生成可以粘贴到实际源代码中的有效代码。
In Lisp this whole process has been formalized by the macro system. Sure it enables you to create extensions to the syntax and do all sorts of fancy things, but it's actual usefulness is summed up by the above. Of course it helps that the Lisp macro system allows you to manipulate these "snippets" with the full power of the entire language.
在Lisp中,整个过程已经由宏系统正式化。当然它可以让你创建语法的扩展并做各种奇特的事情,但它的实际用处总结如上。当然,Lisp宏系统允许您使用整个语言的全部功能来操纵这些“片段”。
#11
5
In short, macros are transformations of code. They allow to introduce many new syntax constructs. E.g., consider LINQ in C#. In lisp, there are similar language extensions that are implemented by macros (e.g., built-in loop construct, iterate). Macros significantly decrease code duplication. Macros allow embedding «little languages» (e.g., where in c#/java one would use xml to configure, in lisp the same thing can be achieved with macros). Macros may hide difficulties of using libraries usage.
简而言之,宏是代码的转换。它们允许引入许多新的语法结构。例如,考虑C#中的LINQ。在lisp中,存在由宏实现的类似语言扩展(例如,内置循环构造,迭代)。宏显着减少代码重复。宏允许嵌入«小语言»(例如,在c#/ java中使用xml进行配置,在lisp中,使用宏可以实现相同的功能)。宏可能会隐藏使用库的困难。
E.g., in lisp you can write
例如,在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))
and this hides all the database stuff (transactions, proper connection closing, fetching data, etc.) whereas in C# this requires creating SqlConnections, SqlCommands, adding SqlParameters to SqlCommands, looping on SqlDataReaders, properly closing them.
这隐藏了所有数据库内容(事务,正确连接关闭,获取数据等),而在C#中,这需要创建SqlConnections,SqlCommands,将SqlParameters添加到SqlCommands,在SqlDataReaders上循环,正确关闭它们。
#12
2
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.
虽然上面都解释了宏是什么,甚至有很酷的例子,但我认为宏和普通函数之间的关键区别在于LISP在调用函数之前首先评估所有参数。对于宏,它是相反的,LISP将未评估的参数传递给宏。例如,如果将(+ 1 2)传递给函数,函数将接收值3.如果将其传递给宏,它将收到一个List(+ 1 2)。这可以用来做各种非常有用的东西。
- 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
添加新的控制结构,例如循环或解构列表
#13
0
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. 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 ...)
."
很明显,我们不能将setq2定义为函数。如果x = 50且y = -5,则此函数将接收值50,-5和11;它不知道应该设置哪些变量。我们真正想说的是,当你(Lisp系统)看到(setq2 v1 v2 e)时,将它视为等同于(progn(setq v1 e)(setq v2 e))。实际上,这不太对,但它现在会做。宏允许我们通过指定用于将输入模式(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
#14
-2
In python you have decorators, you basically have a function that takes another function as input. You can do what ever you want: call the function, do something else, wrap the function call in a resource acquire release, etc. but you don't get to peek inside that function. Say we wanted to make it more powerful, say your decorator received the code of the function as a list then you could not only execute the function as is but you can now execute parts of it, reorder lines of the function etc.
在python中你有装饰器,你基本上有一个函数,它将另一个函数作为输入。你可以做任何你想做的事情:调用函数,做一些其他事情,在资源获取版本中包装函数调用等等,但是你不必查看该函数。假设我们想让它更强大,说你的装饰器收到函数的代码作为列表然后你不仅可以执行函数,但你现在可以执行它的一部分,重新排序函数的行等。
#1
269
To give the short answer, macros are used for defining language syntax extensions to Common Lisp or Domain Specific Languages (DSLs). These languages are embedded right into the existing Lisp code. Now, the DSLs can have syntax similar to Lisp (like Peter Norvig's Prolog Interpreter for Common Lisp) or completely different (e.g. Infix Notation Math for Clojure).
简而言之,宏用于定义Common Lisp或Domain Specific Languages(DSL)的语言语法扩展。这些语言嵌入到现有的Lisp代码中。现在,DSL可以具有类似于Lisp的语法(如Peter Norvig的Prolog Interpreter for Common Lisp)或完全不同(例如,用于Clojure的Infix表示法数学)。
Here is a more concrete example:
Python has list comprehensions built into the language. This gives a simple syntax for a common case. The line
这是一个更具体的例子:Python具有内置于该语言中的列表推导。这为常见案例提供了简单的语法。这条线
divisibleByTwo = [x for x in range(10) if x % 2 == 0]
yields a list containing all even numbers between 0 and 9. Back in the Python 1.5 days there was no such syntax; you'd use something more like this:
产生一个包含0到9之间所有偶数的列表。回到Python 1.5天,没有这样的语法;你会使用更像这样的东西:
divisibleByTwo = []
for x in range( 10 ):
if x % 2 == 0:
divisibleByTwo.append( x )
These are both functionally equivalent. Let's invoke our suspension of disbelief and pretend Lisp has a very limited loop macro that just does iteration and no easy way to do the equivalent of list comprehensions.
这些都是功能相同的。让我们暂停怀疑,并假装Lisp有一个非常有限的循环宏,它只是迭代而没有简单的方法来完成相当于列表推导。
In Lisp you could write the following. I should note this contrived example is picked to be identical to the Python code not a good example of Lisp code.
在Lisp中,您可以编写以下内容。我应该注意这个人为的例子与Python代码相同,而不是Lisp代码的一个很好的例子。
;; the following two functions just make equivalent of Python's range function
;; you can safely ignore them unless you are running this code
(defun range-helper (x)
(if (= x 0)
(list x)
(cons x (range-helper (- x 1)))))
(defun range (x)
(reverse (range-helper (- x 1))))
;; equivalent to the python example:
;; define a variable
(defvar divisibleByTwo nil)
;; loop from 0 upto and including 9
(loop for x in (range 10)
;; test for divisibility by two
if (= (mod x 2) 0)
;; append to the list
do (setq divisibleByTwo (append divisibleByTwo (list x))))
Before I go further, I should better explain what a macro is. It is a transformation performed on code by code. That is, a piece of code, read by the interpreter (or compiler), which takes in code as an argument, manipulates and the returns the result, which is then run in-place.
在我走得更远之前,我应该更好地解释宏是什么。它是按代码执行的转换。也就是说,由解释器(或编译器)读取的一段代码,它接受代码作为参数,操作并返回结果,然后就地运行。
Of course that's a lot of typing and programmers are lazy. So we could define DSL for doing list comprehensions. In fact, we're using one macro already (the loop macro).
当然,很多打字和程序员都很懒惰。所以我们可以定义DSL来做列表推导。实际上,我们已经使用了一个宏(循环宏)。
Lisp defines a couple of special syntax forms. The quote ('
) indicates the next token is a literal. The quasiquote or backtick (`
) indicates the next token is a literal with escapes. Escapes are indicated by the comma operator. The literal '(1 2 3)
is the equivalent of Python's [1, 2, 3]
. You can assign it to another variable or use it in place. You can think of `(1 2 ,x)
as the equivalent of Python's [1, 2, x]
where x
is a variable previously defined. This list notation is part of the magic that goes into macros. The second part is the Lisp reader which intelligently substitutes macros for code but that is best illustrated below:
Lisp定义了几种特殊的语法形式。引号(')表示下一个标记是文字。 quasiquote或反引号(`)表示下一个标记是带有转义的文字。转义由逗号运算符指示。文字'(1 2 3)相当于Python的[1,2,3]。您可以将其分配给另一个变量或在适当的位置使用它。您可以将`(1 2,x)视为Python的[1,2,x]的等价物,其中x是先前定义的变量。这个列表符号是进入宏的魔法的一部分。第二部分是Lisp阅读器,它智能地将宏替换为代码,但最好说明如下:
So we can define a macro called lcomp
(short for list comprehension). It's syntax will be exactly like the python that we used in the example [x for x in range(10) if x % 2 == 0]
- (lcomp x for x in (range 10) if (= (% x 2) 0))
所以我们可以定义一个名为lcomp的宏(列表推导的简称)。它的语法将与我们在示例中使用的python完全相同[x,如果x%2 == 0则x在范围(10)中 - (lcomp x表示x in(范围10)if(=(%x 2)) 0))
(defmacro lcomp (expression for var in list conditional conditional-test)
;; create a unique variable name for the result
(let ((result (gensym)))
;; the arguments are really code so we can substitute them
;; store nil in the unique variable name generated above
`(let ((,result nil))
;; var is a variable name
;; list is the list literal we are suppose to iterate over
(loop for ,var in ,list
;; conditional is if or unless
;; conditional-test is (= (mod x 2) 0) in our examples
,conditional ,conditional-test
;; and this is the action from the earlier lisp example
;; result = result + [x] in python
do (setq ,result (append ,result (list ,expression))))
;; return the result
,result)))
Now we can execute at the command line:
现在我们可以在命令行执行:
CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)
Pretty neat, huh? Now it doesn't stop there. You have a mechanism, or a paintbrush, if you like. You can have any syntax you could possibly want. Like Python or C#'s with
syntax. Or .NET's LINQ syntax. In end, this is what attracts people to Lisp - ultimate flexibility.
挺整洁的,对吧?现在它并不止于此。如果你愿意,你有一个机制或画笔。您可以拥有任何您可能想要的语法。像Python或C#的语法。或.NET的LINQ语法。最后,这是吸引人们使用Lisp的最佳灵活性。
#2
97
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 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.
但是Lisp是不同的。 Lisp宏可以访问解析器,它是一个非常简单的解析器。 Lisp宏不是一个字符串,而是一个列表形式的预处理源代码,因为Lisp程序的源不是字符串;这是一个清单。 Lisp程序非常善于拆分列表并将它们重新组合在一起。他们每天都可靠地做到这一点。
Here is an extended example. Lisp has a macro, called "setf", that performs assignment. The simplest form of setf is
这是一个扩展的例子。 Lisp有一个名为“setf”的宏,它执行赋值。最简单的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; you can use the "car" and "cdr" functions to get the first element of a list or the rest of the list, respectively.
Lisp也有名单;您可以使用“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". But you do not have to remember "rplaca", because you can write
现在,如果您想用新值替换列表的第一个元素,该怎么办?这样做有一个标准功能,令人难以置信的是,它的名字甚至比“汽车”更糟糕。这是“rplaca”。但你不必记住“rplaca”,因为你可以写
(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:
这里真正发生的是“setf”是一个宏。在编译时,它检查它的参数,并且它看到第一个具有形式(汽车SOMETHING)。它对自己说“哦,程序员正试图设置汽车的东西。用于它的功能是'rplaca'。”它悄悄地将代码重写为:
(rplaca somelist whatever)
#3
51
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. There's also no way you can write a C macro to do the job for you.
例如,在C中,switch / case结构只适用于整数类型,如果你想将它用于浮点数或字符串,你可以使用嵌套的if语句和显式比较。你也无法编写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. It's not a wrong way to go about it, but it's not necessarily the easiest.
要在C中执行相同操作,您必须编写一个自定义预处理器,它会占用您的初始(非常C)源并吐出C编译器可以理解的内容。这不是一个错误的方法,但它不一定是最容易的。
#4
38
Lisp macros allow you to decide when (if at all) any part or expression will be evaluated. To put a simple example, think of C's:
Lisp宏允许您决定何时(如果有的话)将评估任何部分或表达式。举一个简单的例子,想想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:
你可以用宏做的其他事情是创建新的关键字和/或迷你语言(查看(循环...)宏的例子),将其他语言集成到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.
希望这可以帮助。
#5
28
I don't think I've ever seen Lisp macros explained better than by this fellow: http://www.defmacro.org/ramblings/lisp.html
我认为我从未见过Lisp宏解释得比这个人更好:http://www.defmacro.org/ramblings/lisp.html
#6
10
A lisp macro takes a program fragment as input. This program fragment is represented a data structure which can be manipulated and transformed any way you like. In the end the macro outputs another program fragment, and this fragment is what is executed at runtime.
lisp宏将程序片段作为输入。该程序片段表示一种数据结构,可以按照您喜欢的方式进行操作和转换。最后,宏输出另一个程序片段,这个片段是在运行时执行的。
C# does not have a macro facility, however an equivalent would be if the compiler parsed the code into a CodeDOM-tree, and passed that to a method, which transformed this into another CodeDOM, which is then compiled into IL.
C#没有宏工具,但是如果编译器将代码解析为CodeDOM树,并将其传递给方法,则将其转换为另一个CodeDOM,然后将其编译为IL。
This could be used to implement "sugar" syntax like the for each
-statement using
-clause, linq select
-expressions and so on, as macros that transforms into the underlying code.
这可以用于实现“糖”语法,如for-statement using-clause,linq select-expressions等,作为转换为底层代码的宏。
If Java had macros, you could implement Linq syntax in Java, without needing Sun to change the base language.
如果Java有宏,您可以在Java中实现Linq语法,而无需Sun更改基本语言。
Here is pseudo-code for how a lisp-style macro in C# for implementing using
could look:
下面是C#中实现使用的lisp样式宏如何看起来的伪代码:
define macro "using":
using ($type $varname = $expression) $block
into:
$type $varname;
try {
$varname = $expression;
$block;
} finally {
$varname.Dispose();
}
#7
9
Think of what you can do in C or C++ with macros and templates. They're very useful tools for managing repetitive code, but they're limited in quite severe ways.
想想你可以用C和C ++用宏和模板做什么。它们是管理重复代码的非常有用的工具,但它们受到严格限制。
- Limited macro/template syntax restricts their use. For example, you can't write a template which expands to something other than a class or a function. Macros and templates can't easily maintain internal data.
- The complex, very irregular syntax of C and C++ makes it difficult to write very general macros.
有限的宏/模板语法限制了它们的使用。例如,您不能编写扩展为类或函数以外的模板的模板。宏和模板无法轻松维护内部数据。
C和C ++的复杂,非常不规则的语法使得编写非常通用的宏变得困难。
Lisp and Lisp macros solve these problems.
Lisp和Lisp宏解决了这些问题。
- Lisp macros are written in Lisp. You have the full power of Lisp to write the macro.
- Lisp has a very regular syntax.
Lisp宏是用Lisp编写的。你有Lisp的全部功能来编写宏。
Lisp有一个非常规则的语法。
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. Or all the crazy tricks in (excellent) books like Modern C++ Design, which are still tough to debug and (in practice) non-portable between real-world compilers even though the language has been standardised for a decade. All of that melts away if the langauge you use for metaprogramming is the same language you use for programming!
与任何掌握了C ++的人交谈,并询问他们花了多长时间学习模板元编程所需的模板技巧。或者像(现代C ++设计)这些(优秀)书籍中的所有疯狂技巧,即使语言已经标准化了十年,仍然难以调试和(实际上)在现实编译器之间不可移植。如果用于元编程的语言与用于编程的语言相同,那么所有这些都会消失!
#8
8
I'm not sure I can add some insight to everyone's (excellent) posts, but...
我不确定我是否可以为每个人的(优秀)帖子添加一些见解,但......
Lisp macros work great because of the Lisp syntax nature.
由于Lisp语法本质,Lisp宏工作得很好。
Lisp is an extremely regular language (think of everything is a list); macros enables you to treat data and code as the same (no string parsing or other hacks are needed to modify lisp expressions). You combine these two features and you have a very clean way to modify code.
Lisp是一种非常规则的语言(想想一切都是一个列表);宏使您可以将数据和代码视为相同(不需要字符串解析或其他修改来修改lisp表达式)。您将这两个功能结合起来,并且有一种非常简洁的方法来修改代码。
Edit: What I was trying to say is that Lisp is homoiconic, which means that the data structure for a lisp program is written in lisp itself.
编辑:我想说的是Lisp是homoiconic,这意味着lisp程序的数据结构是用lisp本身编写的。
So, you end up with a way of creating your own code generator on top of the language using the language itself with all its power (eg. 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这样的某些框架允许您使用不同的方法做到这一点,它从根本上说是一个黑客攻击)。
In practice, with macros you end up building your own mini-language on top of lisp, without the need to learn additional languages or tooling, and with using the full power of the language itself.
在实践中,使用宏,您最终可以在lisp之上构建自己的迷你语言,而无需学习其他语言或工具,并且可以充分利用语言本身的强大功能。
#9
8
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 ++的人交谈,并问他们花了多长时间学习模板元编程所需的所有模板技巧[仍然没有那么强大]。
... 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这样的一些框架允许你使用不同的方法来实现这一点,但它从根本上说是一个黑客攻击。
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.
DOLIST类似于Perl的foreach或Python的。作为JSR-201的一部分,Java在Java 1.5中添加了类似的循环结构和“增强”for循环。注意宏有什么区别。一个Lisp程序员在他们的代码中注意到一个共同的模式可以编写一个宏来给自己提供该模式的源级抽象。注意到相同模式的Java程序员必须说服Sun,这种特殊的抽象值得添加到该语言中。然后Sun必须发布一个JSR并召集一个行业范围的“专家组”来解决所有问题。根据Sun的说法,这个过程平均需要18个月。之后,编译器编写者都必须升级他们的编译器以支持新功能。甚至一旦Java程序员最喜欢的编译器支持新版本的Java,他们可能“仍然”不能使用新功能,直到它们被允许破坏与旧版Java的源兼容性。因此,Common Lisp程序员可以在五分钟内解决自己的麻烦,困扰Java程序员多年。
—Peter Seibel, in "Practical Common Lisp"
-Peter Seibel,在“Practical Common Lisp”中
#10
6
Lisp macros represents a pattern that occurs in almost any sizeable programming project. Eventually in a large program you have a certain section of code where you realize it would be simpler and less error prone for you to write a program that outputs source code as text which you can then just paste in.
Lisp宏代表几乎任何大型编程项目中出现的模式。最终在一个大型程序中你有一段代码,你会发现它会更简单,更容易出错,你可以编写一个程序,输出源代码作为文本然后你可以粘贴。
In Python objects have two methods __repr__
and __str__
. __str__
is simply the human readable representation. __repr__
returns a representation that is valid Python code, which is to say, something that can be entered into the interpreter as valid Python. This way you can create little snippets of Python that generate valid code that can be pasted into your actually source.
在Python中,对象有两个方法__repr__和__str__。 __str__只是人类可读的表示。 __repr__返回一个有效Python代码的表示,也就是说,可以作为有效的Python输入到解释器中的东西。通过这种方式,您可以创建一小段Python,生成可以粘贴到实际源代码中的有效代码。
In Lisp this whole process has been formalized by the macro system. Sure it enables you to create extensions to the syntax and do all sorts of fancy things, but it's actual usefulness is summed up by the above. Of course it helps that the Lisp macro system allows you to manipulate these "snippets" with the full power of the entire language.
在Lisp中,整个过程已经由宏系统正式化。当然它可以让你创建语法的扩展并做各种奇特的事情,但它的实际用处总结如上。当然,Lisp宏系统允许您使用整个语言的全部功能来操纵这些“片段”。
#11
5
In short, macros are transformations of code. They allow to introduce many new syntax constructs. E.g., consider LINQ in C#. In lisp, there are similar language extensions that are implemented by macros (e.g., built-in loop construct, iterate). Macros significantly decrease code duplication. Macros allow embedding «little languages» (e.g., where in c#/java one would use xml to configure, in lisp the same thing can be achieved with macros). Macros may hide difficulties of using libraries usage.
简而言之,宏是代码的转换。它们允许引入许多新的语法结构。例如,考虑C#中的LINQ。在lisp中,存在由宏实现的类似语言扩展(例如,内置循环构造,迭代)。宏显着减少代码重复。宏允许嵌入«小语言»(例如,在c#/ java中使用xml进行配置,在lisp中,使用宏可以实现相同的功能)。宏可能会隐藏使用库的困难。
E.g., in lisp you can write
例如,在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))
and this hides all the database stuff (transactions, proper connection closing, fetching data, etc.) whereas in C# this requires creating SqlConnections, SqlCommands, adding SqlParameters to SqlCommands, looping on SqlDataReaders, properly closing them.
这隐藏了所有数据库内容(事务,正确连接关闭,获取数据等),而在C#中,这需要创建SqlConnections,SqlCommands,将SqlParameters添加到SqlCommands,在SqlDataReaders上循环,正确关闭它们。
#12
2
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.
虽然上面都解释了宏是什么,甚至有很酷的例子,但我认为宏和普通函数之间的关键区别在于LISP在调用函数之前首先评估所有参数。对于宏,它是相反的,LISP将未评估的参数传递给宏。例如,如果将(+ 1 2)传递给函数,函数将接收值3.如果将其传递给宏,它将收到一个List(+ 1 2)。这可以用来做各种非常有用的东西。
- 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
添加新的控制结构,例如循环或解构列表
#13
0
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. 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 ...)
."
很明显,我们不能将setq2定义为函数。如果x = 50且y = -5,则此函数将接收值50,-5和11;它不知道应该设置哪些变量。我们真正想说的是,当你(Lisp系统)看到(setq2 v1 v2 e)时,将它视为等同于(progn(setq v1 e)(setq v2 e))。实际上,这不太对,但它现在会做。宏允许我们通过指定用于将输入模式(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
#14
-2
In python you have decorators, you basically have a function that takes another function as input. You can do what ever you want: call the function, do something else, wrap the function call in a resource acquire release, etc. but you don't get to peek inside that function. Say we wanted to make it more powerful, say your decorator received the code of the function as a list then you could not only execute the function as is but you can now execute parts of it, reorder lines of the function etc.
在python中你有装饰器,你基本上有一个函数,它将另一个函数作为输入。你可以做任何你想做的事情:调用函数,做一些其他事情,在资源获取版本中包装函数调用等等,但是你不必查看该函数。假设我们想让它更强大,说你的装饰器收到函数的代码作为列表然后你不仅可以执行函数,但你现在可以执行它的一部分,重新排序函数的行等。