1 Scheme Macro
Scheme Macro:将一个语法形式转换成另一个语法形式。 A macro is a syntactic form with an associated transformer that expands the original form into existing forms.
从语言实现的角度看,宏(scheme macro)是对语言编译器的扩展。宏允许我们定义新的语法形式, 而不需要编译器做任何改变。
(define-syntax-rule (swap a b)
(let ([tmp a])
(set! a b)
(set! b tmp)))
(let ([a 100] [b 200])
(swap a b)
(printf "~a\n" (list a b)))
2 Scheme Macro语法
最简单的创建一个宏的方式是使用 define-syntax-rule :
(define-syntax-rule pattern template)
;; 一个具体的例子,交换两个变量的值
(define-syntax-rule (swap a b)
(let ([tmp a])
(set! a b)
(set! b tmp)))
pattern中的symbol成为 pattern variable, 在template中所有的pattern variable会被具体的 实际调用时候的语法对象所替代。
define-syntax 和 syntax-rules : define-syntax-rule 只能匹配一个pattern,但是使用define-syntax和syntax-rules,我们 可以在一个宏里写出多个pattern-template。
(define-syntax id
(syntax-rules (literal-id ...)
[pattern template]
...))
;;一个具体的例子
(define-syntax rotate
(syntax-rules ()
[(rotate a b) (swap a b)]
[(rotate a b c) (begin
(swap a b)
(swap b c))]))
pattern可以支持sequence … , 用来表示一个或者多个syntax object.
(define-syntax rotate
(syntax-rules ()
[(rotate a) (void)]
[(rotate a b c ...) (begin
(swap a b)
(rotate b c ...))]))
procedure macro: 使用syntax-rules我们不能对pattern variable做更多判断(譬如判断macro的参数是否合法等),不能对 template做更多操作。
(define-syntax swap
(lambda (stx)
(syntax-case stx ()
[(swap x y)
(if (and (identifier? #'x)
(identifier? #'y))
#'(let ([tmp x])
(set! x y)
(set! y tmp))
(raise-syntax-error #f
"not an identifier"
stx
(if (identifier? #'x)
#'y
#'x)))])))
这里对swap参数做了检查,如果这样调用 (swap 10 b) 将会报错,因为10不是一个identifier
transformer: define-syntax 创建一个 transformer ,且绑定一个名字,这个绑定的名字能在编译的时候 用来展开表达式(expand expression)。
(define-syntax a
(lambda (stx)
#'(printf "zh\n")))
(a)
当然transformer一般使用syntax-rules定义,syntax-rules返回的是一个procedure:
(syntax-rules () [(nothing) something])
#<procedure>
(syntax-case #'(+ 1 2) ()
[(op n1 n2) #'(- n1 n2)])
'(- 1 2)
3 Hygienic macros
Hygienic(安全)是对Scheme Macro系统描述用的最多的一个词,一般来说,hygienic macros用来表示 表示宏的展开式里引入的变量不会和宏所使用的环境中的变量名冲突。
一个比较准确的描述:
If a macro transformer inserts a binding for an identifier, the new binding will
not capture other identifiers of the same name introduced elsewhere.
举例来说:
(define-syntax-rule (swap a b)
(let ([tmp a])
(set! a b)
(set! b tmp)))
(let ([tmp 100] [b 200])
(swap tmp b)
(printf "~a\n" (list tmp b)))
(swap tmp b) 展开后定义了一个局部变量 tmp ,他们会swap所使用的环境中的 tmp 不会有 任何关系,他们不会发生冲突。
另一个常用来形容scheme macro特点的词汇是 referential transparency ,如果一个宏展开式 中引用了一个free variable(非local variable), 那么这个free variable将和宏定义的环境绑定, 而和宏具体使用环境无关。这有点像lexical scoping。
(define-syntax-rule (swap a b)
(let ([tmp a])
(set! a b)
(set! b tmp)))
(let ([set! 100] [b 200])
(swap set! b)
(printf "~a\n" (list set! b)))
在swap的定义里使用了let, set!这两个free variable,他们绑定的是swap定义处的环境,为global namespace。 在swap使用的环境中,可以看到set!已经被定义成一个number,但是这不会对swap展开式中的set!有任何影响。
当然现在一般使用 hygienic macro 同时表示上面两个scheme macro的特性。
4 Syntax Object:
macro transformer的输入输出为syntax object。一个S-exp对应的syntax object包含了值:(quote S-exp), source-location,lexical-information(用来保证hygienic特性). source-location一般是parse 源代码的时候 加入(看另一篇文章racket reader). 创建一个syntax Object很简单:
(syntax (+ 1 2))
#<syntax:1:0 (+ 1 2)>
5 C Macro
通过 #define 的形式定义的pre-processor,他是C语言的重要组成部分。C的宏能帮我们做很多事情, 譬如定义常量,省些重复代码,内联代码等。
6 Scheme Macro 与 C Macro的比较
C宏的优点暂且不说,这里只说下缺点。C的宏在某些情况下,比较难以得到安全,可靠的转换后的代码。
C的宏允许我们在宏的实现里写入任意字符串。
#define foo "hello
printf(foo world")
这个宏连lexical tokens都不是完整的(此时一个完整的lexcial token为"hello world"). 这对阅读代码,编辑器分析程序源码都是很痛苦的事。我们说这种宏:failed to respect the integrity of lexical tokens。
C的宏可以展开成任意词法序列:
#define mul(a, b) a*b
add(x-y, x+y)
exand to:
x-y*x+y
正因为此,我们在初次学习C的宏的时候,就会被告知,宏的实现体里一定要把参数括起来!但即便如此, 我在实际工作中还是出现了忘了括小括号,结果导致了错误。这种现象叫做:failed to respect the structure of expressions。
我们在宏内使用的名字可能和宏使用的环境的名字冲突:
#define swap(v, w) { int tmp = (v);\
(v) = (w); (w) = tmp;}
int tmp =
补充:综合编程 , 其他综合 ,