Appearance
tl;dr 本文介绍 Oleg 在 [1] 里提出的用 R5RS syntax-rules 实现的看起来并不 hygiene 的宏。
本文目前只是笔记。
前置知识
Scheme
本文需要 R5RS 引入的 syntax-rules, 不需要 R6RS 的 syntax-case。事实上,引入 syntax-case 后的宏几乎是最强大的,但仅有 syntax-rules 的宏系统甚至不能对 symbol 做什么操作,比如一个宏不可能把 point
变成 point-x
和 point-y
。而本文仅仅使用 syntax-rule 完成了一些看似不干净的操作。
干净宏/动机
把干净宏的定义说清楚并不是一件容易的事情。 [1] 提到了这样的定义
A macro system is called hygienic, in the general sense, if it avoids inadvertent captures of free variables through systematic renaming
很遗憾,这一定义已经被[1]攻破了,我们可以仅用syntax-rules接管自由变量。另一个更好的定义是> Generated identifiers that become binding instances in the completely expanded program must only bind variables that are generated at the same transcription step.
接管自由变量
在 [1] 中提到了 extract
宏,可以从一个 expression 里提取出特定的 free identifier,实现方式就是用 syntax-rules 遍历表达式树,直到匹配出一个 free identifier。而为了匹配 free identifier,只需将其放进 auxiliary word list 里即可。实现如 [1]
(define-syntax extract
(syntax-rules ()
((_ symb body cont)
(letrec-syntax
([tr
(syntax-rules (symb)
[(_ x symb tail
(cont-head symb-1 . cont-args))
(cont-head (x . symb-1) . cont-args)]
[(_ d (x . y) tail cont)
(tr x x (y . tail) cont)]
[(_ d1 d2 ()
(cont-head symb-1 . cont-args))
(cont-head (symb . symb-1) . cont-args)]
[(_ d1 d2 (x . y) cont)
(tr x x y cont)])])
(tr body body () cont)))))
使用 extract
宏,我们可以污染宏调用里的 free identifier。例如
(define-syntax mbi-dirty-v1
(syntax-rules ()
[(_ val body)
(let-syntax
([cont
(syntax-rules ()
[(_ (symb) val body)
(let ((symb val)) body)])])
(extract i body (cont () val body)))]))
(mbi-dirty-v1 10 (display i))
; => 10
在上面的例子里, extract
把调用方的 i
从 (display i)
里提取出来(而不是自己生成一个 i
),然后生成了一个这样的宏调用
(define-syntax mbi-cont
(syntax-rules ()
[(_ symb val body)
(let ((symb val)) body)]))
(mbi-cont i 10 (display i))
其中调用 mbi-cont
处的 i
是从 (display i)
里提取出来的。
然而,上面的做法并不能接管 bound identifier,例如
(let ((i 1))
(mbi-dirty-v1 10 (display i)))
; => 1
接管绑定变量
为了接管绑定变量,我们需要局部修改 lambda
和 let
的定义,使得每次 lambda
或 let
之后都会局部更新宏定义。 未完待续...