宏是代码转换器。
有几种实现宏语法的方法:
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 宏存在的一个问题是,像 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宏使用了这种方法。
但是在这两个示例中,都需要使用自定义代码遍历源代码。