通用Lisp宏语法关键字:我该如何称呼它?

3
我已经查阅《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宏正好做了这种事情。它被称为什么,我该如何实现它?我尝试宏展开一个样本循环表达式来看看它是如何组成的,但结果代码难以理解。有人可以指引我正确的方向吗?

提前谢谢。


列表推导式已经可以使用可怕的“LOOP”宏。 - SK-logic
1
我知道。我只是为了锻炼而参加的。 - wvoq
3个回答

4

那么,for 循环的本质是解析其作为 body 提供的表单。例如:

(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”解析器之外,这个解决方案还有一个缺点,在Common Lisp中不容易解决,即构建中间列表,如果你想链接你的推导式:
(listc x for x in (listc ...) if (evenp x))

由于Common Lisp中没有yield的道德等效物,因此很难创建一个不需要完全实例化中间结果的机制。一种解决方法是在listc的扩展器中编码可能的“生成器”形式的知识,使扩展器可以在运行时优化/内联基本序列的生成,而无需构造整个中间列表。

另一种方法是引入{{link1:“延迟列表”}}(链接指向方案,因为Common Lisp中没有类似的机制--尽管首先必须构建它,但这并不特别难)。

此外,您总是可以查看其他人的代码,特别是如果他们试图解决相同或类似的问题,例如:


3

宏是代码转换器。

有几种实现宏语法的方法:

  • 解构

Common Lisp 提供了一个宏参数列表,也提供了一种解构形式。当使用宏时,源代码根据参数列表进行解构。

这限制了宏语法的外观,但对于许多宏的用途来说,提供了足够的机制。

请参阅 Common Lisp 中的 Macro Lambda Lists

  • 解析

Common Lisp 还为宏提供了整个宏调用表单的访问权限。然后,宏负责解析该表单。解析器需要由宏作者提供,或者是作者完成的宏实现的一部分。

一个例子是 INFIX 宏:

(infix (2 + x) * (3 + sin (y)))

宏实现需要实现一个中缀解析器并返回前缀表达式:
(* (+ 2 x) (+ 3 (sin y)))
  • 基于规则的

一些Lisp提供语法规则,这些规则与宏调用形式进行匹配。对于匹配的语法规则,相应的转换器将被用来创建新的源代码形式。在Common Lisp中可以很容易地实现这一点,但默认情况下,Common Lisp没有提供这种机制。

参见Scheme中的syntax case

LOOP

为了实现类似LOOP的语法,需要编写一个解析器,在宏中调用该解析器来解析源表达式。请注意,解析器不是针对文本工作,而是针对已经interned的Lisp数据工作。

在过去(1970年代),这曾在Interlisp中使用,称为“交谈Lisp”,这是一种具有更自然语言般表面的Lisp语法。迭代是其中的一部分,迭代的想法随后被带到其他Lisps中(例如Maclisp的LOOP,从那里又被带到了Common Lisp)。

查看1970年代Warren Teitelmann关于'对话式Lisp'的PDF。

LOOP宏的语法有点复杂,很难看出各个子语句之间的边界。

请查看Common Lisp中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
为了补充Dirk的回答: 编写自己的宏是完全可行的,也许是一个不错的练习。 然而,有几个高质量的这种类型的设施(尽管以lisp惯用方式)可用,例如:

Loop非常具有表现力,但其语法与通用lisp不太相似。一些编辑器不喜欢它并且会缩进不良。但是loop在标准中定义。通常不可能编写扩展到loop。

Iterate更具表现力,并具有熟悉的lispy语法。这不需要任何特殊的缩进规则,因此所有正确缩进lisp的编辑器也将很好地缩进iterate。Iterate不在标准中,因此您必须自己获取它(使用quicklisp)。

Series是一个用于处理序列的框架。在大多数情况下,series将使得不必存储中间值成为可能。


我知道 - 如我上面提到的,我确实知道循环。但是我不知道循环的语法在底层是如何工作的,因此进行了这个练习。 - wvoq

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接