有没有一个简单的例子来解释 Lisp 宏到“通用”?程序员?
我最近与一位同事交谈,并尝试告诉他(Common)Lisp 的美妙之处。我试图以某种方式解释宏,因为我认为宏是 Lisp 的杀手级功能之一,但我失败得很惨——我找不到一个简短、简洁且可以被“凡人”理解的好例子。 “程序员(十年的 Java 经验,一个聪明人,但对“高阶”语言的经验很少)。
如果必须的话,你会如何通过例子解释 Lisp 宏?
I was having a conversation with a colleague recently and tried telling him about the beauty of (Common) Lisp. I tried to explain macros somehow, since I consider macros one of the killer features of Lisp, but I failed rather miserably -- I couldn't find a good example which would be short, concise and understandable by a "mere mortal" programmer (decade of Java experience, a bright guy altogether, but very little experience with "higher-order" languages).
How would you explain Lisp macros by example if you had to?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
根据我的经验,当人们看到宏如何帮助生成过程或其他构造无法生成的代码时,宏会给他们留下最好的印象。通常这样的事情可以被描述为:
其中
总是相同的。以下是此类模式的一些示例:1.
time
宏。 没有宏的语言中的代码将如下所示:您不能将所有公共代码放入过程中,因为它包装了实际代码。 (好的,如果语言支持,您可以创建一个过程并在 lambda 函数中传递实际代码,但这并不总是方便)。
而且,正如您可能知道的那样,在 Lisp 中您只需创建
time
宏并将实际代码传递给它:2.事务。要求 Java 程序员使用 JDBC 编写简单的
SELECT
方法 - 需要 14-17 行,包括打开连接和事务、关闭它们的代码、几个嵌套的try-catch-finally
语句和只有 1 或 2 行唯一代码。在 Lisp 中,您只需编写
with-connection
宏并将代码减少到 2-3 行。3.同步。好吧,Java、C# 和大多数现代语言都已经有同步的语句,但是如果您的语言没有这样的构造怎么办?或者,如果您想引入新的同步类型,例如基于 STM 的事务?同样,您应该为此任务编写单独的类并手动使用它,即在要同步的每个语句周围放置公共代码。
这只是几个例子。您可以提到“不要忘记”宏,例如
with-open
系列,它可以清理环境并保护您免受资源泄漏,新的构造宏例如cond
而不是多个if
,当然,不要忘记像if
、or
和and
这样的惰性结构,不评估他们的论点(与过程应用相反)。一些程序员可能会主张,他们的语言有一种技术可以处理这种或那种情况(ORM、AOP 等),但他们会问,如果存在宏,是否还需要所有这些技术?
因此,将其全部考虑并回答有关如何解释宏的原始问题。将任何广泛使用的 Java 代码(C#、C++ 等)转换为 Lisp,然后将其重写为宏。
From my experience, macros make the best impression on people when they see how it helps to produce code, that cannot be made by the procedures or other constructs. Very often such things may be described as:
where
<common code>
is always the same. Here are some examples of such schema:1. The
time
macro. Code in a language without macros will look something like this:You cannot put all common code to procedure, since it wraps actual code around. (OK, you can make a procedure and pass actual code in lambda function, if the language supports it, but it is not always convenient).
And, as you most probably know, in Lisp you just create
time
macro and pass actual code to it:2. Transactions. Ask Java-programmer to write method for simple
SELECT
with JDBC - it will take 14-17 lines and include code to open connection and transaction, to close them, several nestedtry-catch-finally
statements and only 1 or 2 lines of unique code.In Lisp you just write
with-connection
macro and reduce code to 2-3 lines.3. Synchronization. OK, Java, C# and most of the modern languages already have statements for it, but what to do if your language doesn't have such a construct? Or if you want to introduce new kind of synchronization like STM-based transactions? Again, you should write separate class for this task and work with it manually, i.e. put common code around each statement you want to synchronize.
That was only few examples. You can mention "not-to-forget" macros like
with-open
series, that clean-up environment and protect you from recourses leaks, new constructs macros likecond
instead of multipleif
s, and, of course, don't forget about lazy constructs likeif
,or
andand
, that do not evaluate their arguments (in opposite to procedure application).Some programmers may advocate, that their language has a technology to treat this or that case (ORM, AOP, etc), but ask them, would all these technologies be needed, if macros existed?
So, taking it altogether and answering original question about how to explain macros. Take any widely-used code in Java (C#, C++, etc), transform it to Lisp and then rewrite it as a macro.
新的 WHILE 语句
您的语言设计者忘记了 WHILE 语句。你已经给他发过好几次邮件了。没有成功。从语言版本 2.5、2.6 到 3.0,您一直在等待。什么也没发生...
在 Lisp 中:
(defmacro while ...在此处插入 while 实现...)
完成。
使用 LOOP 的简单实现需要一分钟时间。
根据规范生成代码
然后您可能需要解析呼叫详细记录 (CDR)。您有带有字段描述的记录名称。现在我可以为每一个编写类和方法。我还可以发明一些配置格式,解析配置文件并创建类。在 Lisp 中,我会编写一个宏,根据紧凑的描述生成代码。
请参阅 Lisp 中的领域特定语言,这是一个截屏视频,展示了从工作草图到简单的基于宏的概括的典型开发周期。
代码重写
想象一下,您必须使用 getter 函数访问对象的槽。现在假设您需要在某个代码区域多次访问某些对象。由于某种原因,使用临时变量并不是解决方案。
现在您可以编写一个WITH-GETTER 宏,它为getter 表达式引入一个符号。
该宏将重写封闭块内的源代码,并用 getter 表达式替换所有指定的符号。
New WHILE statement
Your language designer forgot a WHILE statement. You've mailed him several times. No success. You've waited from language version 2.5, 2.6 to 3.0. Nothing happened...
In Lisp:
(defmacro while ... insert your while implementation here ...)
Done.
The trivial implementation using LOOP takes a minute.
Code generation from specifications
Then you may want to parse call detail records (CDRs). You have record names with field descriptions. Now I can write classes and methods for each of those. I could also invent some configuration format, parse a configuration file and create the classes. In Lisp I would write a macro that generates the code from a compact description.
See Domain Specific Languages in Lisp, a screencast showing a typical development cycle from a working sketch to a simple macro based generalization.
Code rewriting
Imagine that you have to access slots of objects using getter functions. Now imagine that you need to access some objects multiple times in some code region. For some reason using temporary variables is no solution.
Now you could write a macro WITH-GETTER which introduces a symbol for the getter expression.
The macro would rewrite the source inside the enclosed block and replace all specified symbols with the getter expression.
由于具体的示例可能会陷入您编写它们所用语言的细节中,请考虑一个不具体但相关的陈述:
“您知道有时必须编写的所有样板代码吗?您永远不必用以下语言编写样板代码 : lisp,因为你总是可以编写一个代码生成器来为你做这件事。”
通过“样板”,我正在考虑 Java 中的一次性接口实现、重写 C++ 中的隐式构造函数、编写 get()-set() 对等。我认为这种修辞策略可能比尝试直接解释宏更有效过于详细,因为他可能非常熟悉各种形式的样板文件,而他从未见过宏。
Since concrete examples can get bogged down in the details of the language you're writing them in, consider a non-concrete but relatable statement:
"You know all that boilerplate code that you sometimes have to write? You never have to write boilerplate in lisp, since you can always write a code-generator to do it for you."
By 'boilerplate', I'm thinking of one-off interface implementations in Java, overriding implicit constructors in c++, writing get()-set() pairs, etc. I think this rhetorical strategy might work better than trying to explain macros directly in too much detail, since he's probably all too familiar with various forms of boilerplate, whereas he's never seen a macro.
我不太了解 CL,但是Scheme 宏可以吗?这是Scheme 中的一个while 循环:
在本例中,该示例演示了您可以轻松地使用宏编写自己的控制结构。
foof-loop
是更多内容的集合有用的循环结构(对于 CL 来说可能没什么新意,但仍然适合演示)。另一个用例:从关联列表中选取值。假设用户将一个列表作为选项传递给您的函数。您可以使用此宏轻松地选取值:
示例用法:
注意
let-assq
宏如何允许选取value
、left
、和“节点”中的右键
键,而无需编写更长的let
形式。I don't know CL well enough, but will Scheme macros do? Here's a while loop in Scheme:
In this case, the example demonstrates that you can easily write your own control structures using macros.
foof-loop
is a collection of even more useful looping constructs (probably nothing new given CL's ones, but still good for a demonstration).Another use case: picking values out of associative lists. Say users pass in an alist as options to your function. You can easily pick values out by using this macro:
Example usage:
Notice how the
let-assq
macro allows picking out thevalue
,left
, andright
keys from the "node" without having to write a much longerlet
form.我将宏视为与函数类似(或双重)的抽象,只不过您可以选择何时以及如何评估参数。这强调了为什么宏很有用——就像函数一样,可以防止代码重复并简化维护。
我最喜欢的例子是照应宏。像 aif、awhile 或 aand:
这些非常简单,可以节省大量打字时间。
I view macros as an abstraction similar (or dual) to functions, except you may choose when and how to evaluate the arguments. This underlines why macros are useful - just like functions are, to prevent code duplication and to ease maintenance.
My favorite example are anaphoric macros. Like aif, awhile or aand:
These are very simple and can save a lot of typing.
这不是你能在短时间内解释清楚的事情,是的,宏的概念可以用一句话来解释,一个例子,比如一段时间,还是很容易理解的,问题是那个人不会真正理解为什么宏是一个很好的东西,只有这样简单的例子。
It's not something you can explain in a short amount of time, well it is, the concept of macro can be explained in one sentence and an example such as a while is fairly easy to understand, the problem is that that person will not really understand why macros are a nice thing to have with only such trivial examples.