常见的lisp宏语法关键字:我甚至称之为什么?

时间:2011-08-20 05:00:21

标签: syntax macros common-lisp keyword

我已经浏览了 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宏确实完成了这类事情。它叫什么,我该如何实现它?我尝试宏扩展一个示例循环表达式,看看它是如何放在一起的,但结果代码是难以理解的。任何人都能引导我朝着正确的方向前进吗?

提前致谢。

3 个答案:

答案 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宏的一个问题是FORFROMWHENCOLLECT等符号与“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是一个处理序列的框架。在大多数情况下,系列可以不存储中间值。