読者です 読者をやめる 読者になる 読者になる

#<syntax (foo)>

プログラミング言語schemeには二種類のマクロがある。潔癖なマクロしか書けないsyntax-rulesと、そうでないものも書けるsyntax-caseだ。しかし、伝統的に多くの処理系がdefine-macroを提供してきた。そこで、自分なりにsyntax-caseでdefine-macroを定義しようと試みた結果がこうなった。

(define-syntax define-macro
  (lambda(x)
    (syntax-case x ()
      ((_ name body)
       (with-syntax ((syntax-body
                      (datum->syntax-object
                       (syntax k)
                       (syntax-object->datum (syntax body)))))
         (syntax
          (define-syntax name
            (lambda(y)
              (syntax-case y ()
                ((name a (... ...))
                 (let* ((m (syntax-object->datum
                            (syntax (a (... ...)))))
                        (n (eval `(syntax-body ,@m)
                                 (interaction-environment))))
                   (with-syntax ((r (datum->syntax-object (syntax k) n)))
                     (syntax r)))))))))))))

どうにもごちゃごちゃしている。自分でもツッコミどころがいろいろとあるのは承知している。r6rsでは廃止されたinteraction-environmentを使っているし、そもそもevalを使わなければならないのか疑問である。
更にはその挙動もdefine-macroらしくない点があると指摘をもらった。以下のようなケースでエラーになる。

(define-macro foo (lambda () '(list x)))
(let ((x 10)) (foo))

先人に頼るのが手っ取り早いだろうと"(define-syntax define-macro"をキーワードとして検索したところ、やはりsyntax-caseでdefine-macroを定義しようとしたものが簡単に見つかった。

(define-syntax define-macro
  (lambda (stx)
    (syntax-case
        stx ()
      ((_ (macro . args) . body)
       (syntax (define-macro macro (lambda args . body))))
      ((_ macro transformer)
       (syntax (define-syntax (macro stx2)
                 (let ((v (syntax->datum stx2)))
                   (datum->syntax stx2
                     (apply transformer (cdr v))))))))))
http://c2.com/cgi/wiki?DefineSyntax

datum->syntaxの第一引数が、第二引数をsyntaxオブジェクトに変換するにあたって適用する文脈のようなものであるというのはおぼろげにわかっていたのだけれど、はっきり理解はしていないので、題材としてとても興味深い。
ところが、これで先程と同じ例を試すと別のエラーになった。

> (define-macro foo (lambda () '(list x)))
> (let ((x 10)) (foo))
Error in datum->syntax: invalid argument #<syntax (foo)>.
Type (debug) to enter the debugger.

ふとした思いつきで下のような形に変形してみた。これだとうまくいく。

(define-syntax define-macro
  (lambda (stx)
    (syntax-case stx ()
      ((_ (macro . args) body)
       (syntax (define-macro macro
                 (lambda args body))))
      ((_ macro transformer)
       (syntax (define-syntax macro
                 (lambda(stx2)
                   (syntax-case stx2 ()
                     ((f . sv)
                      (let ((v (syntax->datum (syntax sv))))
                        (datum->syntax (syntax f)
                          (apply transformer v))))))))))))

でもやっぱりどうしてなのかよくわからない。ちゃんと仕様を読もうと思いつつも積ん読状態のままになっているので、わからなくても一度は目を通すべきかもしれない。
Document ID: e076e8cb76951bf3b8c0955d93fa65d2