我已经浏览了 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
宏确实完成了这类事情。它叫什么,我该如何实现它?我尝试宏扩展一个示例循环表达式,看看它是如何放在一起的,但结果代码是难以理解的。任何人都能引导我朝着正确的方向前进吗?
提前致谢。
答案 0 :(得分:5)
嗯,基本上,它解析了作为主体提供的表单。例如:
(defmacro listc (expr &rest forms)
;;
;;
;; (listc EXP for VAR in GENERATOR [if CONDITION])
;;
;;
(labels ((keyword-p (thing name)
(and (symbolp thing)
(string= name thing))))
(destructuring-bind (for* variable in* generator &rest tail) forms
(unless (and (keyword-p for* "FOR") (keyword-p in* "IN"))
(error "malformed comprehension"))
(let ((guard (if (null tail) 't
(destructuring-bind (if* condition) tail
(unless (keyword-p if* "IF") (error "malformed comprehension"))
condition))))
`(loop
:for ,variable :in ,generator
:when ,guard
:collecting ,expr)))))
(defun range (start end &optional (by 1))
(loop
:for k :upfrom start :below end :by by
:collecting k))
除了我使用的hackish“解析器”之外,这个解决方案还有一个缺点,在常见的lisp中不容易解决,即中间列表的构造,如果你想链接你的理解:
(listc x for x in (listc ...) if (evenp x))
由于在普通的lisp中没有yield
的道德等价物,因此很难创建一个设施,它不需要完全实现中间结果。解决这个问题的一种方法可能是在listc
的扩展器中编码可能的“生成器”形式的知识,因此扩展器可以优化/内联基本序列的生成,而无需在运行时构造整个中间列表-time。
另一种方式可能是引入"lazy lists"(链接指向scheme,因为在常见的lisp中没有相同的工具 - 你必须首先构建它,尽管它并不特别困难)。
此外,您可以随时查看其他人的代码,特别是,如果他们试图解决相同或类似的问题,例如:
答案 1 :(得分:3)
宏是代码转换器。
有几种方法可以实现宏的语法:
Common Lisp提供了一个宏参数列表,它也提供了一种解构形式。使用宏时,根据参数列表对源表单进行解构。
这限制了宏语法的外观,但是对于宏的许多用途提供了足够的机制。
请参阅Common Lisp中的Macro Lambda Lists。
Common Lisp还为宏提供了对整个宏调用表单的访问。然后宏负责解析表单。解析器需要由宏作者提供,或者是作者完成的宏实现的一部分。
一个例子是INFIX宏:
(infix (2 + x) * (3 + sin (y)))
宏实现需要实现一个中缀解析器并返回一个前缀表达式:
(* (+ 2 x) (+ 3 (sin y)))
一些Lisps提供语法规则,这些规则与宏调用表单匹配。对于匹配的语法规则,将使用相应的转换器来创建新的源表单。可以在Common Lisp中轻松实现这一点,但默认情况下,它不是Common Lisp中提供的机制。
请参阅Scheme中的syntax case。
<强> LOOP 强>
为了实现类似LOOP
的语法,需要编写一个在宏中调用的解析器来解析源表达式。请注意,解析器不适用于文本,而是适用于实际的Lisp数据。
在过去(20世纪70年代),这已经在Interlisp中用于所谓的“Conversational Lisp”,这是一种Lisp语法,具有更自然的语言,如表面。迭代是其中的一部分,然后迭代的想法被带到其他Lisps(就像Maclisp的LOOP
,然后从那里被带到Common Lisp)。
请参阅20世纪70年代Warren Teitelmann撰写的关于“Conversational Lisp”的PDF文件。
LOOP
宏的语法有点复杂,并且不容易看到各个子语句之间的界限。
请参阅Common Lisp中的extended syntax for LOOP。
(loop for i from 0 when (oddp i) collect i)
同样如下:
(loop
for i from 0
when (oddp i)
collect i)
LOOP宏的一个问题是FOR
,FROM
,WHEN
和COLLECT
等符号与“COMMON-LISP”包不同(命名空间)。当我现在使用不同的包(命名空间)在源代码中使用LOOP
时,这将导致此源命名空间中的新符号。出于这个原因,有些人喜欢写:
(loop
:for i :from 0
:when (oddp i)
:collect i)
在上面的代码中,LOOP
相关符号的标识符位于KEYWORD名称空间中。
为了使解析和阅读更容易,已经提出将括号带回来。 这种宏用法的示例可能如下所示:
(iter (for i from 0) (when (oddp i) (collect i)))
同样如下:
(iter
(for i from 0)
(when (oddp i)
(collect i)))
在上面的版本中,更容易找到子表达式并遍历它们。
Common Lisp的ITERATE宏使用这种方法。
但在两个示例中,都需要使用自定义代码遍历源代码。
答案 2 :(得分:2)
补充Dirk的答案: 为此编写自己的宏是完全可行的,也许是一个很好的练习。 然而,有很多设施可用于此类事物(虽然以一种惯常的方式),在那里有高质量,例如
Loop非常具有表现力,但其语法与其他常见的lisp不同。有些编辑不喜欢它,并且会缩进很差。但是,循环在标准中定义。通常不可能将扩展写入循环。
迭代更具表现力,并且具有熟悉的lispy语法。这不需要任何特殊的缩进规则,因此所有正确缩进lisp的编辑器也会很好地缩进迭代。迭代不符合标准,因此您必须自己获取(使用quicklisp)。
Series是一个处理序列的框架。在大多数情况下,系列可以不存储中间值。