Skip to content

你的宏干净吗?

tl;dr 本文介绍 Oleg 在 [1] 里提出的用 R5RS syntax-rules 实现的看起来并不 hygiene 的宏。

本文目前只是笔记。

前置知识

Scheme

本文需要 R5RS 引入的 syntax-rules, 不需要 R6RS 的 syntax-case。事实上,引入 syntax-case 后的宏几乎是最强大的,但仅有 syntax-rules 的宏系统甚至不能对 symbol 做什么操作,比如一个宏不可能把 point 变成 point-xpoint-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

接管绑定变量

为了接管绑定变量,我们需要局部修改 lambdalet 的定义,使得每次 lambdalet 之后都会局部更新宏定义。 未完待续...

参考文献