对于使用一流函数无法做到的Lisp宏,你能做些什么?

时间:2021-09-07 01:02:14

I think I understand Lisp macros and their role in the compilation phase.

我想我理解Lisp宏及其在编译阶段的作用。

But in Python, you can pass a function into another function

但是在Python中,您可以将函数传递给另一个函数

def f(filename, g):
  try:                                
     fh = open(filename, "rb") 
     g(fh)
  finally:
     close(fh) 

So, we get lazy evaluation here. What can I do with macros and not with functions as first class objects?

所以,我们在这里得到懒惰的评价。我可以用宏做什么而不用函数作为第一类对象?

7 个解决方案

#1


25  

First of all Lisp has first-class functions too, so you could as well ask: "Why do I need macros in Lisp if I already have first-class functions". The answer to that is that first-class functions don't allow you to play with syntax.

首先,Lisp也有一流的功能,所以你也可以问:“如果我已经拥有一流的功能,为什么我需要在Lisp中使用宏”。答案是,第一类函数不允许您使用语法。

On a cosmetic level, first-class functions allow you to write f(filename, some_function) or f(filename, lambda fh: fh.whatever(x)), but not f(filename, fh, fh.whatever(x)). Though arguably that's a good thing because in that last case it is a lot less clear where fh suddenly comes from.

在化妆品级别上,第一类函数允许您编写f(文件名,some_function)或f(文件名,lambda fh:fh.whatever(x)),但不能写f(文件名,fh,fh.whatever(x)) 。虽然可以说这是一件好事,因为在最后一种情况下,fh突然来自哪里不太清楚。

More importantly functions can only contain code that is valid. So you can't write a higher-order function reverse_function that takes a function as an argument and executes it "in reverse", so that reverse_function(lambda: "hello world" print) would execute print "hello world". With a macro you can do this. Of course this particular example is quite silly, but this ability is enormously useful when embedding domain specific languages.

更重要的是,函数只能包含有效的代码。所以你不能编写一个高阶函数reverse_function,它将一个函数作为一个参数并“反向”执行它,这样reverse_function(lambda:“hello world”print)就会执行print“hello world”。使用宏,您可以执行此操作。当然,这个特定的例子非常愚蠢,但是这种能力在嵌入领域特定语言时非常有用。

For example you couldn't implement common lisp's loop construct in python. Hell, you couldn't even implement python's for ... in construct in python if it wasn't really built-in - at least not with that syntax. Sure you could implement something like for(collection, function), but that's a lot less pretty.

例如,您无法在python中实现常见的lisp循环结构。好吧,如果它不是真正内置的话,你甚至无法在python中的构造中实现python ... - 至少不是那种语法。当然你可以实现像(收集,功能)这样的东西,但这不是那么漂亮。

#2


11  

Here's Matthias Felleisen's answer from 2002 (via http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg01539.html):

这是2002年Matthias Felleisen的答案(来自http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg01539.html):

I'd like to propose that there are three disciplined uses of macros:

我想提出宏的三个用途:

  1. data sublanguages: I can write simple looking expressions and create complex nested lists/arrays/tables with quote, unquote etc neatly dressed up with macros.

    数据子语言:我可以编写简单的表达式并创建复杂的嵌套列表/数组/表,其中包含引号,unquote等,整齐地装饰着宏。

  2. binding constructs: I can introduce new binding constructs with macros. That helps me get rid of lambda's and with placing things closer together that belong together. For example, one of our teachpacks contains a form
    (web-query ([last-name (string-append "Hello " first-name " what's your last name?"]) ... last-name ... first-name ...) with the obvious interaction between a program and a Web consumer implied.
    [Note: In ML you could write web-query(fn last-name => ...)string_append(...) but by golly that's a pain and an unnecessary pattern.]

    绑定结构:我可以用宏引入新的绑定结构。这有助于我摆脱lambda并将更紧密的东西放在一起。例如,我们的一个教学包中包含一个表单(web-query([姓氏(字符串 - 附加“Hello”名字“你的姓氏是什么?”))...姓氏...名字...)与程序和Web消费者之间明显的交互暗示。[注意:在ML中你可以编写web-query(fn last-name => ...)string_append(...)但是通过golly这是一个痛苦和不必要的模式。]

  3. evaluation reordering: I can introduce constructs that delay/postpone the evaluation of expressions as needed. Think of loops, new conditionals, delay/force, etc.
    [Note: In Haskell, you don't need that one.]

    评估重新排序:我可以根据需要引入延迟/推迟表达式评估的结构。想想循环,新条件,延迟/强制等等。[注意:在Haskell中,你不需要那个。]

I understand that Lispers use macros for other reasons. In all honesty, I believe that this is partly due to compiler deficiencies, and partly due to "semantic" irregularities in the target language.

我理解Lispers出于其他原因使用宏。老实说,我认为这部分是由于编译器的缺陷,部分是由于目标语言中的“语义”不规范。

I challenge people to address all three issues when they say language X can do what macros can do.

当他们说X语言可以做宏可以做什么时,我挑战人们解决所有三个问题。

-- Matthias

Felleisen is one of the most influential macro researchers in the field. (I don't know whether he would still agree with this message, though.)

Felleisen是该领域最具影响力的宏观研究人员之一。 (不过我不知道他是否会同意这个消息。)

More reading: Paul Graham's On Lisp (http://www.paulgraham.com/onlisp.html; Graham definitely doesn't agree with Felleisen that these are the only useful uses of macros), and Shriram Krishnamurthi's paper "Automata via Macros" (http://www.cs.brown.edu/~sk/Publications/Papers/Published/sk-automata-macros/).

更多阅读:Paul Graham的On Lisp(http://www.paulgraham.com/onlisp.html; Graham绝对不同意Felleisen这些是宏的唯一有用用途),以及Shriram Krishnamurthi的论文“Automata via Macros” (http://www.cs.brown.edu/~sk/Publications/Papers/Published/sk-automata-macros/)。

#3


8  

Macros are expanded in a compile-time. Closures are constructed in runtime. With macros you can implement highly efficient compilers of embedded domain specific languages, and with high order functions you can only implement inefficient interpreters. That eDSL compilers may do all kinds of static checks, do whatever expensive optimisations you fancy implementing, but when you've got only runtime you can't do anything expensive.

宏在编译时扩展。闭包是在运行时构造的。使用宏,您可以实现嵌入式域特定语言的高效编译器,并且使用高阶函数,您只能实现低效的解释器。 eDSL编译器可以执行各种静态检查,执行您想要实现的任何昂贵的优化,但是当您只有运行时,您就无法做任何昂贵的事情。

And needless to mention that macros allows much more flexible syntax (literally, any syntax) for your eDSLs and language extensions.

不用说,宏为您的eDSL和语言扩展提供了更灵活的语法(字面意思,任何语法)。

See the answers to this question for more details: Collection of Great Applications and Programs using Macros

有关更多详细信息,请参阅此问题的答案:使用宏收集优秀应用程序和程序

#4


7  

Macros do code transformations

宏进行代码转换

The macro transforms source code. A lazy evaluation does not. Imagine that you can now write functions which transform arbitrary code to arbitrary different code.

宏转换源代码。懒惰的评价没有。想象一下,您现在可以编写将任意代码转换为任意不同代码的函数。

Very simple code transformations

非常简单的代码转换

The creation of simple language constructs is also only a very simple example. Consider your example of opening a file:

简单语言结构的创建也只是一个非常简单的例子。考虑打开文件的示例:

(with-open-file (stream file :direction :input)
  (do-something stream))

vs.

(call-with-stream (function do-something)
                  file
                  :direction :input)

What the macro gives me is a slightly different syntax and code structure.

宏给我的是一种略有不同的语法和代码结构。

Embedded language: advanced iteration constructs

嵌入式语言:高级迭代结构

Next consider a slightly different example:

接下来考虑一个稍微不同的例子

(loop for i from 10 below 20 collect (sqr i))

vs.

(collect-for 10 20 (function sqr))

We can define a function COLLECT-FOR which does the same for a simple loop and has variables for start, end and a step function.

我们可以定义一个函数COLLECT-FOR,它对一个简单的循环做同样的事情,并且包含start,end和step函数的变量。

But LOOP provides a new language. The LOOP macro is a compiler for this language. This compiler can do LOOP specific optimizations and can also check the syntax at compile time for this new language. An even more powerful loop macro is ITERATE. These powerful tools on the language level now can be written as libraries without any special compiler support.

但是LOOP提供了一种新语言。 LOOP宏是这种语言的编译器。此编译器可以执行特定于LOOP的优化,还可以在编译时检查此新语言的语法。一个更强大的循环宏是ITERATE。现在,语言级别的这些强大工具可以编写为库,而无需任何特殊的编译器支持。

Walking the code tree in a macro and making changes

在宏中遍历代码树并进行更改

Next another simple example:

下一个简单的例子:

(with-slots (age name) some-person
  (print name)
  (princ " "
  (princ age))

vs. something similar:

与类似的东西:

(flet ((age (person) (slot-value person 'age))
       (name (person) (slot-value person 'name)))
   (print (name))
   (princ " ")
   (princ (age)))

The WITH-SLOTS macro causes the complete walk of the enclosed source tree and replaces the variable name with a call to (SLOT-VALUE SOME-PERSON 'name):

WITH-SLOTS宏导致封闭源树的完整遍历,并通过调用(SLOT-VALUE SOME-PERSON'名称)替换变量名称:

(progn
  (print (slot-value some-person 'name))
  (princ " "
  (princ (slot-value some-person 'age)))

In this case the macro can rewrite selected parts of the code. It understands the structure of the Lisp language and knows that name and age are variables. It also understands that in some situations name and age might not be variables and should not be rewritten. This is an application of a so-called Code Walker, a tool that can walk code trees and make changes to the code tree.

在这种情况下,宏可以重写代码的选定部分。它理解Lisp语言的结构,并且知道名称和年龄是变量。它还理解在某些情况下,名称和年龄可能不是变量,不应该重写。这是一个所谓的Code Walker的应用程序,这是一种可以遍历代码树并对代码树进行更改的工具。

Macros can modify the compile-time environment

宏可以修改编译时环境

Another simple example, the contents of a small file:

另一个简单的例子,一个小文件的内容:

(defmacro oneplus (x)
  (print (list 'expanding 'oneplus 'with x))
  `(1+ ,x))

(defun example (a b)
   (+ (oneplus a) (oneplus (* a b))))

In this example we are not interested in the macro ONEPLUS, but in the macro DEFMACRO itself.

在这个例子中,我们对宏ONEPLUS不感兴趣,但是在宏DEFMACRO本身。

What is interesting about it? In Lisp you can have a file with above contents and use the file compiler to compile that file.

有趣的是什么?在Lisp中,您可以拥有一个包含上述内容的文件,并使用文件编译器来编译该文件。

;;; Compiling file /private/tmp/test.lisp ...
;;; Safety = 3, Speed = 1, Space = 1, Float = 1, Interruptible = 1
;;; Compilation speed = 1, Debug = 2, Fixnum safety = 3
;;; Source level debugging is on
;;; Source file recording is  on
;;; Cross referencing is on
; (TOP-LEVEL-FORM 0)
; ONEPLUS

(EXPANDING ONEPLUS SOURCE A) 
(EXPANDING ONEPLUS SOURCE (* A B)) 
; EXAMPLE
;; Processing Cross Reference Information

So we see, that the file compiler expands the use of the ONEPLUS macro.

所以我们看到,文件编译器扩展了对ONEPLUS宏的使用。

What is special about that? There is a macro definition in the file and in the next form we already use that new macro ONEPLUS. We have never loaded the macro definition into Lisp. Somehow the compiler knows and registers the defined macro ONEPLUS and is then able to use it.

有什么特别之处?文件中有一个宏定义,在下一个表单中我们已经使用了新的宏ONEPLUS。我们从未将宏定义加载到Lisp中。不知何故,编译器知道并注册定义的宏ONEPLUS,然后才能使用它。

So the macro DEFMACRO registers the newly defined macro ONEPLUS in the compile-time environment, so that the compiler knows about this macro - without loading the code. The macro then can be executed at compile-time during macro expansion.

因此宏DEFMACRO在编译时环境中注册新定义的宏ONEPLUS,以便编译器知道此宏 - 无需加载代码。然后宏可以在宏扩展期间在编译时执行。

With a function we can't do that. The compiler creates code for function calls, but does not run them. But a macro can be run at compile time and add 'knowledge' to the compiler. This knowledge then is valid during the run of the compiler and partially forgotten later. DEFMACRO is a macro which executes at compile time and then informs the compile-time environment of a new macro.

有了功能,我们不能这样做。编译器为函数调用创建代码,但不运行它们。但是宏可以在编译时运行并向编译器添加“知识”。这种知识在编译器运行期间是有效的,稍后会被遗忘。 DEFMACRO是一个宏,它在编译时执行,然后通知新宏的编译时环境。

Note also that the macro ONEPLUS is also run twice, since it is used twice in the file. The side effect is that it prints something. But ONEPLUS could have also other arbitrary side effects. For example it could check the enclosed source against a rule base and alert you if for example the enclosed code violates some rules (think of a style checker).

另请注意,宏ONEPLUS也会运行两次,因为它在文件中使用了两次。副作用是它打印出一些东西。但ONEPLUS也可能有其他任意的副作用。例如,它可以针对规则库检查封闭的源,并在例如附带的代码违反某些规则时提醒您(考虑样式检查器)。

That means, that a macro, here DEFMACRO, can change the language and its environment during compilation of a file. In other languages the compiler might provide special compiler directives which will be recognized during compilation. There are many examples for such defining macros influencing the compiler: DEFUN, DEFCLASS, DEFMETHOD, ...

这意味着,一个宏,这里是DEFMACRO,可以在编译文件时改变语言及其环境。在其他语言中,编译器可能提供特殊的编译器指令,这些指令在编译期间将被识别。这种定义宏有很多影响编译器的例子:DEFUN,DEFCLASS,DEFMETHOD,......

Macros can make the user code shorter

宏可以缩短用户代码

A typical example is the DEFSTRUCT macro for defining record-like data structures.

一个典型的例子是DEFSTRUCT宏,用于定义类似记录的数据结构。

(defstruct person name age salary)

Above defstruct macro creates code for

以上defstruct宏创建代码

  • a new structure type person with three slots
  • 一个有三个插槽的新结构类型的人

  • slot accessors for reading and writing the values
  • 用于读取和写入值的插槽访问器

  • a predicate to check if some object is of class person
  • 一个谓词来检查某个对象是否属于类人

  • a make-person function to create structure objects
  • 一个make-person函数来创建结构对象

  • a printed representation
  • 印刷品

Additionally it may:

此外,它可能:

  • record the source code
  • 记录源代码

  • record the origin of the source code (file, editor buffer, REPL, ...)
  • 记录源代码的来源(文件,编辑器缓冲区,REPL,...)

  • cross-reference the source code
  • 交叉引用源代码

The original code to define the structure is a short line. The expanded code is much longer.

定义结构的原始代码是一条短线。扩展代码要长得多。

The DEFSTRUCT macro does not need access to a meta-level of the language to create these various things. It just transforms a compact piece of descriptive code into the, typically longer, defining code using the typical language constructs.

DEFSTRUCT宏不需要访问语言的元级别来创建这些不同的东西。它只是使用典型的语言结构将一小段描述性代码转换为通常较长的定义代码。

#5


4  

Instead of a high-level answer, here's a concrete suggestion: read Shriram's The Swine Before Perl. I shows how to develop a macro that is is doing several different things -- a specific control flow, a binding, and a data language. (Plus, you'll see how to actually do this kind of stuff.)

这里有一个具体的建议,而不是一个高级答案:阅读Shriram的Perl之前的The Swine。我展示了如何开发一个正在做几个不同事情的宏 - 一个特定的控制流,一个绑定和一个数据语言。 (另外,你会看到如何真正做到这一点。)

#6


2  

Macros are most useful in languages which use the same form for data and code, because a macro treats the code as data and produces new code. A macro expansion is a just-in-time code generation, which is performed during a compilation phase before the evaluation starts. This makes it very easy to design DSLs.

宏在使用相同形式的数据和代码的语言中最有用,因为宏将代码视为数据并生成新代码。宏扩展是即时代码生成,在评估开始之前的编译阶段执行。这使得设计DSL变得非常容易。

In Python you can not take some code, pass it to a function, which generates new code, in order to execute the new code. In order to achieve something like macros in Python you have to generate an AST from your Python code, modify the AST and evaluate the modified AST.

在Python中,您无法获取某些代码,将其传递给生成新代码的函数,以便执行新代码。为了在Python中实现像宏一样的东西,你必须从Python代码生成一个AST,修改AST并评估修改后的AST。

This means that you can not write an if statement in Python. You can use only the existing one, but you can not modify it or write you own statements. But Lisp macros allow you to write your own statements. For example you can write a fi statement which behaves like if but takes the else part as its first argument and the then part as the second.

这意味着您无法在Python中编写if语句。您只能使用现有的,但不能修改它或编写自己的语句。但Lisp宏允许您编写自己的语句。例如,您可以编写一个fi语句,其行为类似于if,但将else部分作为其第一个参数,然后将then部分作为第二个参数。

The following article describes the difference between macros and procedures more detailed: ftp://ftp.cs.utexas.edu/pub/garbage/cs345/schintro-v13/schintro_130.html

下面的文章描述了宏和程序之间的区别更详细:ftp://ftp.cs.utexas.edu/pub/garbage/cs345/schintro-v13/schintro_130.html

#7


0  

In an example other than lisp, for example elixir, the if control flow statement is in fact a macro. The if is implemented as a function. But in order to have a cleaner more memorable syntax, it was also implemented as a macro.

在除了lisp之外的示例中,例如elixir,if控制流语句实际上是宏。 if是作为函数实现的。但为了获得更清晰,更难忘的语法,它也被实现为宏。

if true do 1+2 end
if true, do: ( 1+2 )
if(true, do: 1+2)
if(true, [do: (1+2)])
if(true, [{:do, 1+2}])

All of the above are equivalent. But the first line is the macro implementation of if, which presumably gets expanded into the if function below.

以上所有都是等价的。但第一行是if的宏实现,可能会扩展到下面的if函数。

By making if a function and accessible as a macro gives you this cool ability to put if control flows inside a parameter of another function, while preserving familiarity with other languages.

通过制作一个函数并且可以作为一个宏访问,如果控件在另一个函数的参数内流动,同时保持对其他语言的熟悉程度,那么这个函数就会很强大。

is_number(if true do 1+2 end)
is_number(if true, do: (1+2))

So I think Macros allow you to control syntax better, thus allowing you to create DSLs that standard functions cannot.

所以我认为宏可以让你更好地控制语法,从而允许你创建标准函数不能的DSL。

#1


25  

First of all Lisp has first-class functions too, so you could as well ask: "Why do I need macros in Lisp if I already have first-class functions". The answer to that is that first-class functions don't allow you to play with syntax.

首先,Lisp也有一流的功能,所以你也可以问:“如果我已经拥有一流的功能,为什么我需要在Lisp中使用宏”。答案是,第一类函数不允许您使用语法。

On a cosmetic level, first-class functions allow you to write f(filename, some_function) or f(filename, lambda fh: fh.whatever(x)), but not f(filename, fh, fh.whatever(x)). Though arguably that's a good thing because in that last case it is a lot less clear where fh suddenly comes from.

在化妆品级别上,第一类函数允许您编写f(文件名,some_function)或f(文件名,lambda fh:fh.whatever(x)),但不能写f(文件名,fh,fh.whatever(x)) 。虽然可以说这是一件好事,因为在最后一种情况下,fh突然来自哪里不太清楚。

More importantly functions can only contain code that is valid. So you can't write a higher-order function reverse_function that takes a function as an argument and executes it "in reverse", so that reverse_function(lambda: "hello world" print) would execute print "hello world". With a macro you can do this. Of course this particular example is quite silly, but this ability is enormously useful when embedding domain specific languages.

更重要的是,函数只能包含有效的代码。所以你不能编写一个高阶函数reverse_function,它将一个函数作为一个参数并“反向”执行它,这样reverse_function(lambda:“hello world”print)就会执行print“hello world”。使用宏,您可以执行此操作。当然,这个特定的例子非常愚蠢,但是这种能力在嵌入领域特定语言时非常有用。

For example you couldn't implement common lisp's loop construct in python. Hell, you couldn't even implement python's for ... in construct in python if it wasn't really built-in - at least not with that syntax. Sure you could implement something like for(collection, function), but that's a lot less pretty.

例如,您无法在python中实现常见的lisp循环结构。好吧,如果它不是真正内置的话,你甚至无法在python中的构造中实现python ... - 至少不是那种语法。当然你可以实现像(收集,功能)这样的东西,但这不是那么漂亮。

#2


11  

Here's Matthias Felleisen's answer from 2002 (via http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg01539.html):

这是2002年Matthias Felleisen的答案(来自http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg01539.html):

I'd like to propose that there are three disciplined uses of macros:

我想提出宏的三个用途:

  1. data sublanguages: I can write simple looking expressions and create complex nested lists/arrays/tables with quote, unquote etc neatly dressed up with macros.

    数据子语言:我可以编写简单的表达式并创建复杂的嵌套列表/数组/表,其中包含引号,unquote等,整齐地装饰着宏。

  2. binding constructs: I can introduce new binding constructs with macros. That helps me get rid of lambda's and with placing things closer together that belong together. For example, one of our teachpacks contains a form
    (web-query ([last-name (string-append "Hello " first-name " what's your last name?"]) ... last-name ... first-name ...) with the obvious interaction between a program and a Web consumer implied.
    [Note: In ML you could write web-query(fn last-name => ...)string_append(...) but by golly that's a pain and an unnecessary pattern.]

    绑定结构:我可以用宏引入新的绑定结构。这有助于我摆脱lambda并将更紧密的东西放在一起。例如,我们的一个教学包中包含一个表单(web-query([姓氏(字符串 - 附加“Hello”名字“你的姓氏是什么?”))...姓氏...名字...)与程序和Web消费者之间明显的交互暗示。[注意:在ML中你可以编写web-query(fn last-name => ...)string_append(...)但是通过golly这是一个痛苦和不必要的模式。]

  3. evaluation reordering: I can introduce constructs that delay/postpone the evaluation of expressions as needed. Think of loops, new conditionals, delay/force, etc.
    [Note: In Haskell, you don't need that one.]

    评估重新排序:我可以根据需要引入延迟/推迟表达式评估的结构。想想循环,新条件,延迟/强制等等。[注意:在Haskell中,你不需要那个。]

I understand that Lispers use macros for other reasons. In all honesty, I believe that this is partly due to compiler deficiencies, and partly due to "semantic" irregularities in the target language.

我理解Lispers出于其他原因使用宏。老实说,我认为这部分是由于编译器的缺陷,部分是由于目标语言中的“语义”不规范。

I challenge people to address all three issues when they say language X can do what macros can do.

当他们说X语言可以做宏可以做什么时,我挑战人们解决所有三个问题。

-- Matthias

Felleisen is one of the most influential macro researchers in the field. (I don't know whether he would still agree with this message, though.)

Felleisen是该领域最具影响力的宏观研究人员之一。 (不过我不知道他是否会同意这个消息。)

More reading: Paul Graham's On Lisp (http://www.paulgraham.com/onlisp.html; Graham definitely doesn't agree with Felleisen that these are the only useful uses of macros), and Shriram Krishnamurthi's paper "Automata via Macros" (http://www.cs.brown.edu/~sk/Publications/Papers/Published/sk-automata-macros/).

更多阅读:Paul Graham的On Lisp(http://www.paulgraham.com/onlisp.html; Graham绝对不同意Felleisen这些是宏的唯一有用用途),以及Shriram Krishnamurthi的论文“Automata via Macros” (http://www.cs.brown.edu/~sk/Publications/Papers/Published/sk-automata-macros/)。

#3


8  

Macros are expanded in a compile-time. Closures are constructed in runtime. With macros you can implement highly efficient compilers of embedded domain specific languages, and with high order functions you can only implement inefficient interpreters. That eDSL compilers may do all kinds of static checks, do whatever expensive optimisations you fancy implementing, but when you've got only runtime you can't do anything expensive.

宏在编译时扩展。闭包是在运行时构造的。使用宏,您可以实现嵌入式域特定语言的高效编译器,并且使用高阶函数,您只能实现低效的解释器。 eDSL编译器可以执行各种静态检查,执行您想要实现的任何昂贵的优化,但是当您只有运行时,您就无法做任何昂贵的事情。

And needless to mention that macros allows much more flexible syntax (literally, any syntax) for your eDSLs and language extensions.

不用说,宏为您的eDSL和语言扩展提供了更灵活的语法(字面意思,任何语法)。

See the answers to this question for more details: Collection of Great Applications and Programs using Macros

有关更多详细信息,请参阅此问题的答案:使用宏收集优秀应用程序和程序

#4


7  

Macros do code transformations

宏进行代码转换

The macro transforms source code. A lazy evaluation does not. Imagine that you can now write functions which transform arbitrary code to arbitrary different code.

宏转换源代码。懒惰的评价没有。想象一下,您现在可以编写将任意代码转换为任意不同代码的函数。

Very simple code transformations

非常简单的代码转换

The creation of simple language constructs is also only a very simple example. Consider your example of opening a file:

简单语言结构的创建也只是一个非常简单的例子。考虑打开文件的示例:

(with-open-file (stream file :direction :input)
  (do-something stream))

vs.

(call-with-stream (function do-something)
                  file
                  :direction :input)

What the macro gives me is a slightly different syntax and code structure.

宏给我的是一种略有不同的语法和代码结构。

Embedded language: advanced iteration constructs

嵌入式语言:高级迭代结构

Next consider a slightly different example:

接下来考虑一个稍微不同的例子

(loop for i from 10 below 20 collect (sqr i))

vs.

(collect-for 10 20 (function sqr))

We can define a function COLLECT-FOR which does the same for a simple loop and has variables for start, end and a step function.

我们可以定义一个函数COLLECT-FOR,它对一个简单的循环做同样的事情,并且包含start,end和step函数的变量。

But LOOP provides a new language. The LOOP macro is a compiler for this language. This compiler can do LOOP specific optimizations and can also check the syntax at compile time for this new language. An even more powerful loop macro is ITERATE. These powerful tools on the language level now can be written as libraries without any special compiler support.

但是LOOP提供了一种新语言。 LOOP宏是这种语言的编译器。此编译器可以执行特定于LOOP的优化,还可以在编译时检查此新语言的语法。一个更强大的循环宏是ITERATE。现在,语言级别的这些强大工具可以编写为库,而无需任何特殊的编译器支持。

Walking the code tree in a macro and making changes

在宏中遍历代码树并进行更改

Next another simple example:

下一个简单的例子:

(with-slots (age name) some-person
  (print name)
  (princ " "
  (princ age))

vs. something similar:

与类似的东西:

(flet ((age (person) (slot-value person 'age))
       (name (person) (slot-value person 'name)))
   (print (name))
   (princ " ")
   (princ (age)))

The WITH-SLOTS macro causes the complete walk of the enclosed source tree and replaces the variable name with a call to (SLOT-VALUE SOME-PERSON 'name):

WITH-SLOTS宏导致封闭源树的完整遍历,并通过调用(SLOT-VALUE SOME-PERSON'名称)替换变量名称:

(progn
  (print (slot-value some-person 'name))
  (princ " "
  (princ (slot-value some-person 'age)))

In this case the macro can rewrite selected parts of the code. It understands the structure of the Lisp language and knows that name and age are variables. It also understands that in some situations name and age might not be variables and should not be rewritten. This is an application of a so-called Code Walker, a tool that can walk code trees and make changes to the code tree.

在这种情况下,宏可以重写代码的选定部分。它理解Lisp语言的结构,并且知道名称和年龄是变量。它还理解在某些情况下,名称和年龄可能不是变量,不应该重写。这是一个所谓的Code Walker的应用程序,这是一种可以遍历代码树并对代码树进行更改的工具。

Macros can modify the compile-time environment

宏可以修改编译时环境

Another simple example, the contents of a small file:

另一个简单的例子,一个小文件的内容:

(defmacro oneplus (x)
  (print (list 'expanding 'oneplus 'with x))
  `(1+ ,x))

(defun example (a b)
   (+ (oneplus a) (oneplus (* a b))))

In this example we are not interested in the macro ONEPLUS, but in the macro DEFMACRO itself.

在这个例子中,我们对宏ONEPLUS不感兴趣,但是在宏DEFMACRO本身。

What is interesting about it? In Lisp you can have a file with above contents and use the file compiler to compile that file.

有趣的是什么?在Lisp中,您可以拥有一个包含上述内容的文件,并使用文件编译器来编译该文件。

;;; Compiling file /private/tmp/test.lisp ...
;;; Safety = 3, Speed = 1, Space = 1, Float = 1, Interruptible = 1
;;; Compilation speed = 1, Debug = 2, Fixnum safety = 3
;;; Source level debugging is on
;;; Source file recording is  on
;;; Cross referencing is on
; (TOP-LEVEL-FORM 0)
; ONEPLUS

(EXPANDING ONEPLUS SOURCE A) 
(EXPANDING ONEPLUS SOURCE (* A B)) 
; EXAMPLE
;; Processing Cross Reference Information

So we see, that the file compiler expands the use of the ONEPLUS macro.

所以我们看到,文件编译器扩展了对ONEPLUS宏的使用。

What is special about that? There is a macro definition in the file and in the next form we already use that new macro ONEPLUS. We have never loaded the macro definition into Lisp. Somehow the compiler knows and registers the defined macro ONEPLUS and is then able to use it.

有什么特别之处?文件中有一个宏定义,在下一个表单中我们已经使用了新的宏ONEPLUS。我们从未将宏定义加载到Lisp中。不知何故,编译器知道并注册定义的宏ONEPLUS,然后才能使用它。

So the macro DEFMACRO registers the newly defined macro ONEPLUS in the compile-time environment, so that the compiler knows about this macro - without loading the code. The macro then can be executed at compile-time during macro expansion.

因此宏DEFMACRO在编译时环境中注册新定义的宏ONEPLUS,以便编译器知道此宏 - 无需加载代码。然后宏可以在宏扩展期间在编译时执行。

With a function we can't do that. The compiler creates code for function calls, but does not run them. But a macro can be run at compile time and add 'knowledge' to the compiler. This knowledge then is valid during the run of the compiler and partially forgotten later. DEFMACRO is a macro which executes at compile time and then informs the compile-time environment of a new macro.

有了功能,我们不能这样做。编译器为函数调用创建代码,但不运行它们。但是宏可以在编译时运行并向编译器添加“知识”。这种知识在编译器运行期间是有效的,稍后会被遗忘。 DEFMACRO是一个宏,它在编译时执行,然后通知新宏的编译时环境。

Note also that the macro ONEPLUS is also run twice, since it is used twice in the file. The side effect is that it prints something. But ONEPLUS could have also other arbitrary side effects. For example it could check the enclosed source against a rule base and alert you if for example the enclosed code violates some rules (think of a style checker).

另请注意,宏ONEPLUS也会运行两次,因为它在文件中使用了两次。副作用是它打印出一些东西。但ONEPLUS也可能有其他任意的副作用。例如,它可以针对规则库检查封闭的源,并在例如附带的代码违反某些规则时提醒您(考虑样式检查器)。

That means, that a macro, here DEFMACRO, can change the language and its environment during compilation of a file. In other languages the compiler might provide special compiler directives which will be recognized during compilation. There are many examples for such defining macros influencing the compiler: DEFUN, DEFCLASS, DEFMETHOD, ...

这意味着,一个宏,这里是DEFMACRO,可以在编译文件时改变语言及其环境。在其他语言中,编译器可能提供特殊的编译器指令,这些指令在编译期间将被识别。这种定义宏有很多影响编译器的例子:DEFUN,DEFCLASS,DEFMETHOD,......

Macros can make the user code shorter

宏可以缩短用户代码

A typical example is the DEFSTRUCT macro for defining record-like data structures.

一个典型的例子是DEFSTRUCT宏,用于定义类似记录的数据结构。

(defstruct person name age salary)

Above defstruct macro creates code for

以上defstruct宏创建代码

  • a new structure type person with three slots
  • 一个有三个插槽的新结构类型的人

  • slot accessors for reading and writing the values
  • 用于读取和写入值的插槽访问器

  • a predicate to check if some object is of class person
  • 一个谓词来检查某个对象是否属于类人

  • a make-person function to create structure objects
  • 一个make-person函数来创建结构对象

  • a printed representation
  • 印刷品

Additionally it may:

此外,它可能:

  • record the source code
  • 记录源代码

  • record the origin of the source code (file, editor buffer, REPL, ...)
  • 记录源代码的来源(文件,编辑器缓冲区,REPL,...)

  • cross-reference the source code
  • 交叉引用源代码

The original code to define the structure is a short line. The expanded code is much longer.

定义结构的原始代码是一条短线。扩展代码要长得多。

The DEFSTRUCT macro does not need access to a meta-level of the language to create these various things. It just transforms a compact piece of descriptive code into the, typically longer, defining code using the typical language constructs.

DEFSTRUCT宏不需要访问语言的元级别来创建这些不同的东西。它只是使用典型的语言结构将一小段描述性代码转换为通常较长的定义代码。

#5


4  

Instead of a high-level answer, here's a concrete suggestion: read Shriram's The Swine Before Perl. I shows how to develop a macro that is is doing several different things -- a specific control flow, a binding, and a data language. (Plus, you'll see how to actually do this kind of stuff.)

这里有一个具体的建议,而不是一个高级答案:阅读Shriram的Perl之前的The Swine。我展示了如何开发一个正在做几个不同事情的宏 - 一个特定的控制流,一个绑定和一个数据语言。 (另外,你会看到如何真正做到这一点。)

#6


2  

Macros are most useful in languages which use the same form for data and code, because a macro treats the code as data and produces new code. A macro expansion is a just-in-time code generation, which is performed during a compilation phase before the evaluation starts. This makes it very easy to design DSLs.

宏在使用相同形式的数据和代码的语言中最有用,因为宏将代码视为数据并生成新代码。宏扩展是即时代码生成,在评估开始之前的编译阶段执行。这使得设计DSL变得非常容易。

In Python you can not take some code, pass it to a function, which generates new code, in order to execute the new code. In order to achieve something like macros in Python you have to generate an AST from your Python code, modify the AST and evaluate the modified AST.

在Python中,您无法获取某些代码,将其传递给生成新代码的函数,以便执行新代码。为了在Python中实现像宏一样的东西,你必须从Python代码生成一个AST,修改AST并评估修改后的AST。

This means that you can not write an if statement in Python. You can use only the existing one, but you can not modify it or write you own statements. But Lisp macros allow you to write your own statements. For example you can write a fi statement which behaves like if but takes the else part as its first argument and the then part as the second.

这意味着您无法在Python中编写if语句。您只能使用现有的,但不能修改它或编写自己的语句。但Lisp宏允许您编写自己的语句。例如,您可以编写一个fi语句,其行为类似于if,但将else部分作为其第一个参数,然后将then部分作为第二个参数。

The following article describes the difference between macros and procedures more detailed: ftp://ftp.cs.utexas.edu/pub/garbage/cs345/schintro-v13/schintro_130.html

下面的文章描述了宏和程序之间的区别更详细:ftp://ftp.cs.utexas.edu/pub/garbage/cs345/schintro-v13/schintro_130.html

#7


0  

In an example other than lisp, for example elixir, the if control flow statement is in fact a macro. The if is implemented as a function. But in order to have a cleaner more memorable syntax, it was also implemented as a macro.

在除了lisp之外的示例中,例如elixir,if控制流语句实际上是宏。 if是作为函数实现的。但为了获得更清晰,更难忘的语法,它也被实现为宏。

if true do 1+2 end
if true, do: ( 1+2 )
if(true, do: 1+2)
if(true, [do: (1+2)])
if(true, [{:do, 1+2}])

All of the above are equivalent. But the first line is the macro implementation of if, which presumably gets expanded into the if function below.

以上所有都是等价的。但第一行是if的宏实现,可能会扩展到下面的if函数。

By making if a function and accessible as a macro gives you this cool ability to put if control flows inside a parameter of another function, while preserving familiarity with other languages.

通过制作一个函数并且可以作为一个宏访问,如果控件在另一个函数的参数内流动,同时保持对其他语言的熟悉程度,那么这个函数就会很强大。

is_number(if true do 1+2 end)
is_number(if true, do: (1+2))

So I think Macros allow you to control syntax better, thus allowing you to create DSLs that standard functions cannot.

所以我认为宏可以让你更好地控制语法,从而允许你创建标准函数不能的DSL。