たまにdefine-macroすると間違える
er-macroが早ければ次もしくは次の次のバージョンに入るのでそれを待ってください。
なんと。。(そんなに直ぐ入るとは思っていなかったので)
前回( http://d.hatena.ne.jp/mjt/20140914/p1 )のdefine-inject-syntaxのようなマクロはdefine-macroとsyntax-rulesが有れば実装できると思ってたけど、冷静に考えるとstraightforwardな方法は無さそう。letrecのような必要な構文要素の挿入のためにはsyntax-rulesで書いたマクロを呼べば良いと思ってたけど、それだと結局2つのマクロをエクスポートして呼び分けるしか無くなってしまう。
あと、Gaucheのdefine-macroでハマったのは:
(define (w f) (define h (macroexpand-1 f)) (print h) (unless (equal? f h) (display "\n===========\n\n") (w h))) (define-macro (ItIs10/body body) `(let ((it 10)) ,body)) (define-macro (ItIs10 body) (list ItIs10/body body)) (print "Expand ItIs10/body:") (w '(ItIs10/body (print it))) (print "Expand ItIs10:") (w '(ItIs10 (print it))) (newline) (print "ItIs10Alt:") (ItIs10/body (print it)) (print "ItIs10:") (ItIs10 (print it))
ItIs10/bodyはitに10をbindするマクロで、ItIs10はItIs10/bodyを呼び出すマクロになっている。Expand結果の見た目は同じだが、ItIs10は期待通りに動作しない。
Expand ItIs10/body: (let ((it 10)) (print it)) =========== (let ((it 10)) (print it)) Expand ItIs10: (#<macro ItIs10/body> (print it)) =========== (let ((it 10)) (print it)) =========== (let ((it 10)) (print it)) ItIs10Alt: 10 ItIs10: *** ERROR: unbound variable: it Stack Trace: _______________________________________ 0 it 1 (print it) At line 78 of "./g.scm"
これは、
(define-macro (ItIs10 body) (list 'ItIs10/body body))
のようにItIs10/bodyにquoteをつけるとちゃんと動く。。scmではちゃんと動いたのに... と思ってたら、写したときにquoteを落していた。
マクロのデバッグはなかなか難しい。前回の define-inject-syntax も、マクロのトレースが最初から有るRacketとnmoshで全パタン(syntax-case、er-macro-transformer、lisp-transformer)を実装して、それからchibi-scheme等の他の処理系で動作確認している。nmoshは他のSchemeフロントエンドと比較してシンプルなのでデバッグしやすいが、今回のような問題を見逃してしまう(元がsyntax-caseなのでtransformerをquoteしない場合は値として扱えてしまう)。
不健全マクロは一年に1、2回も書かないので全然上手くならない気がする。syntax-rulesは日常的に書くけど。。そして偶にsyntax-caseを使うと、他と比べて表現力が高くて感心する。