Common Lisp 宏语法关键字:我该怎么称呼它?
为了自己回答这个问题,我浏览了On Lisp、Practical Common Lisp和SO档案,但这些尝试因我无法命名这个概念而受挫我很感兴趣。如果有人能告诉我这类事情的规范术语,我将不胜感激。
这个问题可能最好通过一个例子来解释。假设我想在 Common Lisp 中实现 Python 风格的列表推导式。在Python中我会写:
[x*2 for x in range(1,10) if x > 3]
所以我首先写下:
(listc (* 2 x) x (range 1 10) (> x 3))
然后定义一个宏,将上面的内容转换为正确的理解。到目前为止,一切都很好。
然而,对于尚未熟悉 Python 列表推导式的读者来说,该表达式的解释是不透明的。我真正希望能够写的是以下内容:
(listc (* 2 x) for x in (range 1 10) if (> x 3))
但我一直无法找到 Common Lisp 术语。看起来 loop
宏正是做这种事情的。它叫什么,我该如何实现它?我尝试宏扩展示例循环表达式以查看它是如何组合在一起的,但生成的代码难以理解。有人能引导我走向正确的方向吗?
提前致谢。
I've looked through On Lisp, Practical Common Lisp and the SO archives in order to answer this on my own, but those attempts were frustrated by my inability to name the concept I'm interested in. I would be grateful if anyone could just tell me the canonical term for this sort of thing.
This question is probably best explained by an example. Let's say I want to implement Python-style list comprehensions in Common Lisp. In Python I would write:
[x*2 for x in range(1,10) if x > 3]
So I begin by writing down:
(listc (* 2 x) x (range 1 10) (> x 3))
and then defining a macro that transforms the above into the correct comprehension. So far so good.
The interpretation of that expression, however, would be opaque to a reader not already familiar with Python list comprehensions. What I'd really like to be able to write is the following:
(listc (* 2 x) for x in (range 1 10) if (> x 3))
but I haven't been able to track down the Common Lisp terminology for this. It seems that the loop
macro does exactly this sort of thing. What is it called, and how can I implement it? I tried macro-expanding a sample loop expression to see how it's put together, but the resulting code was unintelligible. Could anyone guide me in the right direction?
Thanks in advance.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
稍微补充一下德克的答案:
为此编写自己的宏是完全可行的,而且也许是一个很好的练习。
然而,有几种高质量的工具可以用于此类事情(尽管是以 lisp 惯用的方式),例如
循环非常具有表现力,但有一个语法与 Common Lisp 的其余部分不同。有些编辑不喜欢它并且缩进效果不好。然而,循环是在标准中定义的。通常不可能将扩展写入循环。
Iterate 更具表现力,并且具有熟悉的 lispy 语法。这不需要任何特殊的缩进规则,因此所有正确缩进 lisp 的编辑器也会很好地缩进迭代。 Iterate 不在标准中,因此您必须自己获取它(使用quicklisp)。
Series 是一个用于处理序列的框架。在大多数情况下,系列可以不存储中间值。
To complement Dirk's answer a little:
Writing your own macros for this is entirely doable, and perhaps a nice exercise.
However there are several facilities for this kind of thing (albeit in a lisp-idiomatic way) out there of high quality, such as
Loop is very expressive, but has a syntax not resembling the rest of common lisp. Some editors don't like it and will indent poorly. However loop is defined in the standard. Usually it's not possible to write extentions to loop.
Iterate is even more expressive, and has a familiar lispy syntax. This doesn't require any special indentation rules, so all editors indenting lisp properly will also indent iterate nicely. Iterate isn't in the standard, so you'll have to get it yourself (use quicklisp).
Series is a framework for working on sequences. In most cases series will make it possible not to store intermediate values.
嗯, for 本质上是解析作为其主体提供的形式。例如:
除了我使用的hackish“解析器”之外,这个解决方案还有一个缺点,在common lisp中不容易解决,即中间列表的构造,如果你想链接你的理解:
因为没有道德等价物对于 Common Lisp 中的
yield
来说,很难创建一个不需要中间结果完全具体化的工具。解决这个问题的一种方法可能是在 listc 的扩展器中对可能的“生成器”形式的知识进行编码,因此扩展器可以优化/内联基本序列的生成,而无需构建整个中间体在运行时列出。另一种方法可能是引入“惰性列表”(链接指向方案,因为在 common lisp 中没有等效的工具——你必须首先构建它,尽管它不是特别难)。
此外,您始终可以查看其他人的代码,特别是如果他们尝试解决相同或类似的问题,例如:
Well, what for does is essentially, that it parses the forms supplied as its body. For example:
Apart from the hackish "parser" I used, this solution has a disadvantage, which is not easily solved in common lisp, namely the construction of the intermediate lists, if you want to chain your comprehensions:
Since there is no moral equivalent of
yield
in common lisp, it is hard to create a facility, which does not require intermediate results to be fully materialized. One way out of this might be to encode the knowledge of possible "generator" forms in the expander oflistc
, so the expander can optimize/inline the generation of the base sequence without having to construct the entire intermediate list at run-time.Another way might be to introduce "lazy lists" (link points to scheme, since there is no equivalent facility in common lisp -- you had to build that first, though it's not particularily hard).
Also, you can always have a look at other people's code, in particular, if they tries to solve the same or a similar problem, for example:
宏是代码转换器。
实现宏语法的方法有多种:
Common Lisp 提供了一个宏参数列表,它也提供了一种解构形式。当使用宏时,源形式根据参数列表被解构。
这限制了宏语法的外观,但为宏的许多用途提供了足够的机制。
请参阅 Common Lisp 中的宏 Lambda 列表。
Common Lisp 还为宏提供了对整个宏调用形式的访问。然后宏负责解析表单。解析器需要由宏作者提供或者是作者完成的宏实现的一部分。
一个例子是 IFIX 宏:
宏实现需要实现一个中缀解析器并返回一个前缀表达式:
一些 Lisp 提供语法规则,这些规则与宏调用形式相匹配。对于匹配的语法规则,相应的转换器将用于创建新的源表单。人们可以在 Common Lisp 中轻松实现这一点,但默认情况下,Common Lisp 中并未提供这种机制。
请参阅Scheme 中的语法大小写。
LOOP
为了实现类似
LOOP
的语法,需要编写一个解析器,在宏中调用该解析器来解析源表达式。请注意,解析器不能处理文本,而是处理内部 Lisp 数据。在过去(1970 年代),这已在 Interlisp 中用于所谓的“Conversational Lisp”,这是一种具有更自然语言的表面的 Lisp 语法。迭代是其中的一部分,迭代思想随后被引入其他 Lisp(例如 Maclisp 的
LOOP
,然后又被引入 Common Lisp)。请参阅 Warren Teitelmann 撰写的有关“Conversational Lisp”的 PDF 20 世纪 70 年代。
LOOP
宏的语法有点复杂,并且不容易看出各个子语句之间的界限。请参阅 Common Lisp 中的LOOP 的扩展语法。
相同:
LOOP 宏的一个问题是
FOR
、FROM
、WHEN
和COLLECT
等符号与“COMMON-LISP”包(命名空间)不同。当我现在使用不同的包(命名空间)在源代码中使用LOOP
时,这将导致此源命名空间中出现新符号。因此,有些人喜欢这样写:在上面的代码中,LOOP 相关符号的标识符位于 KEYWORD 命名空间中。
为了使解析和阅读更容易,建议将括号带回来。
这种宏用法的示例可能如下所示:
相同于:
在上面的版本中,更容易找到子表达式并遍历它们。
Common Lisp 的 ITERATE 宏就使用了这种方法。
但在这两个示例中,都需要使用自定义代码遍历源代码。
Macros are code transformers.
There are several ways of implementing the syntax of a macro:
Common Lisp provides a macro argument list which also provides a form of destructuring. When a macro is used, the source form is destructured according to the argument list.
This limits how macro syntax looks like, but for many uses of Macros provides enough machinery.
See Macro Lambda Lists in Common Lisp.
Common Lisp also gives the macro the access to the whole macro call form. The macro then is responsible for parsing the form. The parser needs to be provided by the macro author or is part of the macro implementation done by the author.
An example would be an INFIX macro:
The macro implementation needs to implement an infix parser and return a prefix expression:
Some Lisps provide syntax rules, which are matched against the macro call form. For a matching syntax rule the corresponding transformer will be used to create the new source form. One can easily implement this in Common Lisp, but by default it is not a provided mechanism in Common Lisp.
See syntax case in Scheme.
LOOP
For the implementation of a
LOOP
-like syntax one needs to write a parser which is called in the macro to parse the source expression. Note that the parser does not work on text, but on interned Lisp data.In the past (1970s) this has been used in Interlisp in the so-called 'Conversational Lisp', which is a Lisp syntax with a more natural language like surface. Iteration was a part of this and the iteration idea has then brought to other Lisps (like Maclisp's
LOOP
, from where it then was brought to Common Lisp).See the PDF on 'Conversational Lisp' by Warren Teitelmann from the 1970s.
The syntax for the
LOOP
macro is a bit complicated and it is not easy to see the boundaries between individual sub-statements.See the extended syntax for LOOP in Common Lisp.
same as:
One problem that the LOOP macro has is that the symbols like
FOR
,FROM
,WHEN
andCOLLECT
are not the same from the "COMMON-LISP" package (a namespace). When I'm now usingLOOP
in source code using a different package (namespace), then this will lead to new symbols in this source namespace. For that reason some like to write:In above code the identifiers for the
LOOP
relevant symbols are in the KEYWORD namespace.To make both parsing and reading easier it has been proposed to bring parentheses back.
An example for such a macro usage might look like this:
same as:
In above version it is easier to find the sub-expressions and to traverse them.
The ITERATE macro for Common Lisp uses this approach.
But in both examples, one needs to traverse the source code with custom code.