For Development HEAD DRAFTSearch (procedure/syntax/module):

5.7 マクロのデバッグ

マクロ展開はコンパイル時に行われるため、デバッグが難しいです。 マクロのデバッグに悩まされるのを避ける最良の方法は、どうしても必要でない限り マクロを書かないこと、そして書くハメになったとしてもできる限り単純なものにすることです。

しかし、時には怪しげなマクロを解きほぐす必要に迫られるという不運に見舞われるかもしれません。 Gaucheにはその助けになるツールがいくつかあります。


5.7.1 マクロ展開をトレースする

マクロトレースは、選択したマクロのマクロ展開器の入力と結果を表示する機能です。 例えば、次のマクロを定義したとしましょう。これはR7RSの7.3節にあるletrecの 定義とだいたい同じです:

(define-syntax my-letrec
  (syntax-rules ()
    [(_ ((var init) ...) body ...)
     (my-letrec "tmps" (var ...) () ((var init) ...) body ...)]
    [(_ "tmps" () (tmp ...) ((var init) ...) body ...)
     (let ((var 'undefined) ...)
       (let ((tmp init) ...)
         (set! var tmp) ...
         body ...))]
    [(_ "tmps" (x y ...) (tmp ...) binds body ...)
     (my-letrec "tmps" (y ...) (newtmp tmp ...) binds body ...)]))

このmy-letrecマクロは、"tmps"をタグにしてループすることで 一時変数を作るというイディオムを使っています。my-letrecをトレースすることで、 このマクロの展開の様子をステップごとに見ることができます:

gosh> (trace-macro 'my-letrec)
(my-letrec)
gosh> (my-letrec [(ev? (^n (if (= n 0) #t (od? (- n 1)))))
                  (od? (^n (if (= n 0) #f (ev? (- n 1)))))]
        (ev? 3))
Macro input>>>
(my-letrec
 ((ev? (^n (if (= n 0) #t (od? (- n 1)))))
  (od? (^n (if (= n 0) #f (ev? (- n 1))))))
 (ev? 3))

Macro output<<<
(my-letrec
 "tmps"
 (ev? od?)
 ()
 ((ev? (^n (if (= n 0) #t (od? (- n 1)))))
  (od? (^n (if (= n 0) #f (ev? (- n 1))))))
 (ev? 3))

Macro input>>>
(my-letrec
 "tmps"
 (ev? od?)
 ()
 ((ev? (^n (if (= n 0) #t (od? (- n 1)))))
  (od? (^n (if (= n 0) #f (ev? (- n 1))))))
 (ev? 3))

Macro output<<<
(my-letrec
 "tmps"
 (od?)
 (newtmp.0)
 ((ev? (^n (if (= n 0) #t (od? (- n 1)))))
  (od? (^n (if (= n 0) #f (ev? (- n 1))))))
 (ev? 3))

Macro input>>>
(my-letrec
 "tmps"
 (od?)
 (newtmp.0)
 ((ev? (^n (if (= n 0) #t (od? (- n 1)))))
  (od? (^n (if (= n 0) #f (ev? (- n 1))))))
 (ev? 3))

Macro output<<<
(my-letrec
 "tmps"
 ()
 (newtmp.1 newtmp.0)
 ((ev? (^n (if (= n 0) #t (od? (- n 1)))))
  (od? (^n (if (= n 0) #f (ev? (- n 1))))))
 (ev? 3))

Macro input>>>
(my-letrec
 "tmps"
 ()
 (newtmp.0 newtmp.1)
 ((ev? (^n (if (= n 0) #t (od? (- n 1)))))
  (od? (^n (if (= n 0) #f (ev? (- n 1))))))
 (ev? 3))

Macro output<<<
(let
 ((ev? (quote undefined)) (od? (quote undefined)))
 (let
  ((newtmp.0 (^n (if (= n 0) #t (od? (- n 1)))))
   (newtmp.1 (^n (if (= n 0) #f (ev? (- n 1))))))
  (set! ev? newtmp.0)
  (set! od? newtmp.1)
  (ev? 3)))

#f

上に示した例において、gosh>プロンプトの次にあるS式がユーザがタイプしたものです。 他はすべて、Macro inputMacro outputのS式を含め、 Gaucheの出力です。

Macro inputの後に示されるS式がマクロ展開器の入力で、 Macro outputの後がその展開結果です。実際のマクロ展開結果は 構文情報が付加されていますが、この出力では読みやすくするために構文情報は取り去られています。

ループの度に同じ名前(newtemp)の変数が導入されていますが、 それらが異なる識別子として扱われていることが展開結果からわかると思います。

デバッグが終わったら、untrace-macroを引数なしで呼んで マクロトレースをオフにしましょう。マクロトレースが設定されていると、 すべてのマクロ展開においてオーバヘッドがかかります。

gosh> (untrace-macro)
#f
Function: trace-macro
Function: trace-macro boolean
Function: trace-macro name-or-pattern …

現在のマクロトレース設定を得たり、変更したりします。 マクロトレース設定は次のいずれかの値を取ります。

#f

マクロトレースはオフです。これがデフォルトの設定です。

#t

全てのマクロの展開がトレースされます。

(name-or-pattern …)

名前がname-or-patternのいずれかにマッチするマクロがトレースされます。 ここで各name-or-patternは、シンボルか正規表現オブジェクトです。 シンボルの場合は名前が完全に一致するもの、正規表現の場合は 名前がそれに一致するものがトレースの対象となります。

引数なしで呼ばれたら、trace-macroは設定を変えず、単に現在の設定を返します。

単一の真偽値を引数に渡した場合は、それをマクロトレースの設定値として、 更新された設定値を返します。

一つ以上のname-or-patternが渡されたら、それらが現在のマクロトレース設定に 追加されます。もし現在の設定が#tなら設定は変わらないことに注意して ください。全てのマクロは既にトレースされているからです。 更新された設定値を返します。

マクロトレースの設定値が#fでない限り、すべてのマクロ展開にオーバヘッドがかかります。 マクロトレースを設定したままにしないようにしてください。

トレース情報は現在のトレース出力ポートに出力されます。 (ポート共通の操作参照)。

Function: untrace-macro
Function: untrace-macro name-or-pattern …

引数なしで呼ばれた場合、マクロトレースをオフにします。

1つ以上のname-or-patternが渡された場合は、現在のマクロトレース設定から それらを除きます。ただし、現在のマクロトレース設定が#t (全てのマクロをトレース) の場合は個別にマクロを除外できません。

更新後のマクロトレース設定を返します。


5.7.2 マクロを自分で展開する

Function: macroexpand form :optional env
Function: macroexpand-1 form :optional env

form がリストで、その最初の要素が大域的にマクロに束縛された 変数であるならば、macroexpand-1はそのマクロ変換子を実行し、 展開されたフォームを返します。そうでなければ、form をそのまま 返します。

macroexpand は、formの一番外側の式がが展開できなくなるまで macroexpand-1 を繰り返します。 (macroexpandformの一番外側以外のマクロは展開しません。 form内の全てのマクロを展開するにはmacroexpand-allを使ってください。)

これらの手続きは、大域的に定義されたマクロを展開するために使うことが できます。

内部的には、衛生マクロは展開されるとformの中にあるシンボルを 構文情報でラップします。しかし、REPLでマクロを展開して確かめたい場合に その情報がついているととても読みにくくなります。 そこで、デフォルトではこれらの手続きは構文情報を取り除いて出力します。 マクロ内で導入された識別子については、必要に応じて名前の衝突を避けるリネームが 行われます。

下の例はmy-letrec (定義はマクロ展開をトレースする参照) を 展開しています。マクロ展開で導入された変数 (newtemp) がリネームされています。

(macroexpand
  '(my-letrec [(ev? (^n (if (= n 0) #t (od? (- n 1)))))
               (od? (^n (if (= n 0) #f (ev? (- n 1)))))]
     (ev? 3)))

⇒

(let
 ((ev? (quote undefined)) (od? (quote undefined)))
 (let
  ((newtmp.0 (^n (if (= n 0) #t (od? (- n 1)))))
   (newtmp.1 (^n (if (= n 0) #f (ev? (- n 1))))))
  (set! ev? newtmp.0)
  (set! od? newtmp.1)
  (ev? 3)))

env引数にモジュールを渡すと、そのモジュールをマクロ使用環境として マクロが展開されます。env#tを渡すと、実行時のモジュールが マクロ使用環境になります。これらの場合は、出力に構文情報が付加されたままになります。

macroexpandの出力を他のマクロ展開結果にプログラム的に埋め込むといった 用途に使う場合には、構文情報を保ったままのフォームが必要になります。

Function: macroexpand-all form :optional env

form中にあるマクロを全て展開します。結果の中に残るのは、 関数呼び出しとGaucheの組み込み構文だけになります。

デフォルト、もしくはenv#tが渡された場合は、 formは実行時のモジュールのトップレベルにあると解釈されます。 モジュールをenvに渡せば、そのモジュールをマクロ使用環境として展開できます。

form中で導入されるローカル変数は全て、衝突を避けるためにリネームされます。 ローカル変数が全て固有の名前を持つようになるので、letフォームはすべて letrecで表されます(letによる束縛が他の束縛をシャドウしないと わかっていれば、letletrecに置き換えても意味は変わりません)。

注意: もしform内で呼ばれているマクロが、他のモジュール中にあるグローバル 変数への参照を挿入した場合、現在の実装ではその情報は失われてしまいます。 いくつか、その問題を修正する方法は考えられるのですが(例えば他のモジュール中に グローバル変数参照は識別子オブジェクトのまま残しておくとか、 with-moduleフォームに変換するとか、 そういったケースのための特殊構文を導入するとか)、今のところどうするか 決まっていません。なので、現在のふるまいにあまり依存しないようにしてください。 今のところ、この手続きは、マクロの展開結果をインタラクティブに確かめる用途に 限って使うのが安全です。

(macroexpand-all
 '(letrec-syntax
      [(when-not (syntax-rules ()
                   [(_ test . body) (if test #f (begin . body))]))]
    (let ([if list])
      (define x (expt foo))
      (let1 x 3
        (when-not (bar) (if x))))))
 ⇒ (letrec ((if.0 list))
     (letrec ((x.1 (expt foo)))
       (letrec ((x.2 '3))
        (if (bar) '#f (if.0 x.2)))))
Special Form: %macroexpand form
Special Form: %macroexpand-1 form


For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT