与其他主流编程语言相比,LISP和Scheme的最大优势是它们的宏系统。与C预处理器和其他宏语言不同,Scheme宏将已解析的代码作为输入,并返回扩展的代码作为输出。这是Scheme的“代码就是数据”短语的应用之一,正是这使得该语言如此强大。
Scheme中的宏是使用创建的define-syntax,可以通过多种方式定义宏。最简单的方法是使用syntax-rules,它使用模式匹配将输入代码转换为输出代码。
这个例子创建一个简单的for item in list和for list as item用于遍历列表中元素的语法:
(define-syntax for (syntax-rules (in as) ; 'in' and 'as' keywords must match in the pattern ; When the 'for' macro is called, try matching this pattern ((for element in list body ...) ; Match one or more body expressions ; Transform the input code (for-each (lambda (element) body ...) list)) ; Try matching another pattern if the first fails ((for list as element body ...) ; Use the existing macro for the transform (for element in list body ...))))
然后可以使用以下两个宏,以提供更强制的样式:
(let ((names '(Alice Bob Eve))) (for name in names (display "Hello ") (display name) (newline)) (for names as name (display "name: ") (display name) (newline)))
运行代码将提供预期的输出:
Hello Alice Hello Bob Hello Eve name: Alice name: Bob name: Eve
要查找的最常见错误是没有将正确的值传递给宏,这通常会导致无用的错误消息,该错误消息适用于扩展形式而不是宏调用。
for上面的语法定义不会检查是否传递了标识符和列表,因此传递任何其他类型将导致指向该for-each调用而不是该for调用的错误。调试它会破坏宏的用途,因此用户应将检查放在其中并报告使用错误,然后可以在编译时捕获该错误。