Syntax Case Examples

例子

syntax-object->datum 的用途

这个函数可以把一个 syntax 对象变成一个 datum, 就像一个 quote 返回的东西那样。我们可以用它来判断两个变量所对应的 symbol 是 否相同。

用下面这个例子可以体验一下 syntax-case:

(define-syntax print-verbatim
  (lambda (x)
    (display x)
    (newline)
    (display (syntax-object->datum x))
    (syntax (newline))))

(print-verbatim "haha")

结果是:

#<syntax:STDIN::6508>
(print-verbatim haha)

第一个 display 显示了 x 是一个 syntax 对象。第二个 display 显示了把 x 转化成一个 datum 之后的结果。其实就是

'(print-verbatim haha)

用法举例:

一般说来 (eq? foo bar) 只能判断两个变量指向的对象是否相等, 而不能判断两个变量的名字是否相同。下面这个 syntax-case 定义 的宏就可以判断这个属性:

(define-syntax symbolic-identifier=?
  (lambda (x)
    (let ((syn (syntax-object->datum x)))
      (if (eq? (cadr syn) (caddr syn))
          (syntax #t)
          (syntax #f)))))

datum->syntax-object 的用途

datum->syntax-object 的语法是:

(datum->syntax-object template-identifier obj)

它可以由一个对象 obj, 构造一个 syntax 对象,这个 syntax 对 象跟 template-identifier 由同样的语法上下文。

这句话有点不好理解,其实它就是说:“就当 obj 是在 template-identifier 出现的时候出现的。”

再不好理解就看看这个例子:

(define-syntax loop
  (lambda (x)
    (syntax-case x ()
      ((k e ...)
       (with-syntax ((break (datum->syntax-object (syntax k) 'break)))
          (syntax (call-with-current-continuation
                    (lambda (break)
                      (let f () e ... (f))))))))))

(let ((n 3) (ls '()))
  (loop
    (if (= n 0) (break ls))
    (set! ls (cons 'a ls))
    (set! n (- n 1))))  (a a a)

这是正确的。

如果我们用通常的办法定义 break, 我们的输入

(loop
    (if (= n 0) (break ls))
    (set! ls (cons 'a ls))
    (set! n (- n 1)))

里面调用 break 时会出错。因为 "break" 这个名字,在 loop 被 定义 时的含义(A),和 loop 被 调用 时的含义(B),是不同的。

所以,我们在 syntax-case 中,把 "break" 这个名字,绑定到由 datum->syntax-object 构造的一个 syntax 对象。这个 syntax 对 象其实就是 break 这个名字,只不过它的上下文是跟 (syntax k), 也就是 "loop" 被 调用 的时候一样。

这样,

(call-with-current-continuation
                    (lambda (break)
                      (let f () e ... (f))))

里面的 "break" 实际上就是指 loop 被调用的时候那个环境里的 break,而不是 loop 被定义的时候的环境里的 break.

这样,在 loop 被调用的时候,break 绑定到了跟当前的 loop 同样 的上下文。所以 loop 的输入里面能够调用到 break.

下面这个例子里。我们定义一个 include, 它跟 load 类似,不过它 读入的文件里的名字就像是在当前调用时被插入的一样。

(define-syntax include
  (lambda (x)
    (define read-file
      (lambda (fn k)
        (let ((p (open-input-file fn)))
          (let f ((x (read p)))
            (if (eof-object? x)
                (begin (close-input-port p) '())
                (cons (datum->syntax-object k x)
                      (f (read p))))))))
    (syntax-case x ()
       ((k filename)
        (let ((fn (syntax-object->datum (syntax filename))))
          (with-syntax (((exp ...) (read-file fn (syntax k))))
            (syntax (begin exp ...))))))))


(let ((x "okay"))
  (include "f-def.scm")
  (f))

如果你有一个文件叫做 f-def.scm 里面有一些内容,比如:

(define f (lambda () x))
(for-each display `("I see! x is: " ,x "\n"))

那么结果会是:

I see! x is: okay
"okay"

这是怎么回事呢?因为经过 datum->syntax-object 的处理, 这条 define 语句

(define f (lambda () x))

就像在 include 被调用时出现的一样,也就是说,它能看到 include 被调用时能看到的一切上下文。所以,这个 define 能够看 到 let 环境内的局部变量 ok,它的值是 "okay".

有关 datum->syntax-object 的另一个例子请看 SyntaxDefinitions 里的那个 syntax-case 的例子.

with-syntax 的定义

with-syntax 可以用 syntax-case 来定义:

(define-syntax with-syntax
  (lambda (x)
    (syntax-case x ()
      ((_ ((p e0) ...) e1 e2 ...)
       (syntax (syntax-case (list e0 ...) ()
                 ((p ...) (begin e1 e2 ...))))))))

有点不好理解,实际上 syntax-case:

(syntax-case exp (literal ...) clause ...)

其中每个 clause 可以是:

(pattern output-expression)
(pattern fender output-expression)

所以 syntax-case 是用 exp 的值,去匹配 pattern.

这样 with-syntax 内部的

(syntax-case (list e0 ...) ()
                 ((p ...) (begin e1 e2 ...)))

就容易理解了。这里我们在用从原来的输入中提取出来的 (list e0 ...) 去匹配 (p ...). 如果我们的输入是:

(with-syntax ((break (datum->syntax-object (syntax k) 'break)))
   ...
   ...)

p 就是 break, e0 就是 (datum->syntax-object (syntax k) 'break).

所以它就被转化成:

......
(syntax-case (list (datum->syntax-object (syntax k) 'break) ...) ()
  ((break ...) (begin e1 e2 ...)))

我们是在用 (datum->syntax-object (syntax k) 'break) 去匹配 break. 那么 break 就得到了 (datum->syntax-object (syntax k) 'break) 的“值”。

那么这个名字 break 就变成了跟 loop 被调用时具有同样语法上下 文的一个名字 --- break. 你可以从 loop 的环境里调用它。