近&況

Recent Posts
Edit

マクロ展開時に副作用を起こすとどうなるか

2016-12-31

R7RSのマクロシステムはsyntax-rulesだけだが、R6RSにあったsyntax-caseや、いくつかの処理系が実装しているexplicit-renaming, implicit-renamingといったマクロシステムでは、マクロ展開時に任意の式が書ける。

では、マクロ展開時にdisplayを使って文字列を出力した場合、それはどのタイミングで評価されるのだろうか?特にインタプリタじゃなくてコンパイラの場合は?

Chicken Scheme

ということで適当なSchemeコンパイラを用意する。Macだとbrew install chickenでChiken Schemeが入る。マクロシステムはexplicit-renamingがあるようなのでそれを使う。

(define-syntax foo
  (er-macro-transformer
    (lambda (expr rename id=?)
      (display "hello")
      (cadr expr))))

(display (foo 1234))

このようにしたとき、「hello」はどのタイミングで出力されるだろうか?

実行してみる

% csc compile-time.scm
hello
% ./compile-time
1234

ということで、コンパイル時に出力されることが分かった。

本来の使い方

やっておいて何だけど、任意の式が書けるというのは別に文字列を出力するためではない。Racketのマニュアルを見ると、「マクロの使い方を間違えたときにより丁寧なエラーメッセージを出せる」というメリットが挙げられている。あとは動的に関数名を組み立てるとか?

(define-syntax define-getter
  (er-macro-transformer
    (lambda (expr rename id=?)
      (let* ((name (symbol->string (cadr expr)))
             (str (string-append "get-" name))
             (sym (string->symbol str)))
        `(define (,sym) '("get" ,(cadr expr)))))))

(define-getter a)   ; (define (get-a) '("get" a))に展開される
(write (get-a))
;=> ("get" a)

追記:マクロ展開時に呼べる関数について

副作用が起こせるといっても、任意の関数が呼べるとは限らないようだ。例えばRacketではbegin-for-syntaxなどでコンパイル時に使う関数だと明示する必要がある。Chickenでも以下はエラーになった。ふーむ。

(define (bar)
  (display "hello"))

(define-syntax foo
  (er-macro-transformer
    (lambda (expr rename id=?)
      (bar)
      (cadr expr))))

(display (foo 1234))
% csc compile-time.scm

Error: during expansion of (foo ...) - unbound variable: bar

        Call history:

        <syntax>          (##core#begin (display (foo 1234)))
        <syntax>          (display (foo 1234))
        <syntax>          (foo 1234)
        <eval>    (bar) <--