ファイヤープロジェクト
マクロ
2004-04-29T19:50+09:00   matsu
コードの生成とマクロについて調査してみた.
evalは引数で渡されたリストを評価する.
> (eval '(+ 1 2 3))
6
> (eval (list '+ 1 2 3))
6
動的に生成したリストをevalに渡せば,そのリストは評価される.この方法で問題となるのは,リストがevalによってコードと解釈され評価されるのが実行時だという点である.これはコストが高く,だいたいの場合,コンパイル時にリストをコードとしてしまう方が効率がよい.
マクロとは式の変換オペレータであり,コンパイル対象は変換後の式である.したがってevalのときのような問題を解決できる.マクロはdefmacroで定義する.
> (defmacro macro-hoge (x)
(list '+ x 1))
MACRO-HOGE
> (macro-hoge 1)
2
マクロ定義を確認するには,macroexpand-1でマクロを展開して表示してみるのがよい.
> (macro-hoge 2)
3
> (macroexpand-1 '(macro-hoge 2))
(+ 2 1) ;
T
(macro-hoge 2)はマクロ展開されて(+ 2 1)となっている.多くの場合マクロは展開するとリストになるように作成する.だが,上のサンプルのように本体がlist式だと少々読みにくく,書きにくい.マクロを展開後の形に近い形式で記述するにはバッククオートを使用する.macro-hogeはバッククオートを使用して以下のように書ける.
> (defmacro macro-hoge (x)
`(+ ,x 1))
MACRO-HOGE
> (macro-hoge 2)
3
> (macroexpand-1 '(macro-hoge 2))
(+ 2 1) ;
T
実はバッククォートは単独で使用するだけなら,シングルクォートと変わらない.
> (defmacro macro-hoge (x)
`(+ x 1))
MACRO-HOGE
> (macroexpand-1 '(macro-hoge 2))
(+ X 1) ;
T
この例とその前の例との違いは,バッククォートの部分が
`(+ ,x 1)
`(+ x 1)
かという違いである.バッククォート内では,(コンマ)に続く式は評価される.
`(+ x 1)
は単に(+ x 1)というリストになるが,
`(+ ,x 1)
はxを評価した結果,マクロの引数が代入される.バッククォート内では,@(コンマアットマーク)も使用できる.,@は引数がリストの場合に,その要素を並べたものとなる.
> (defmacro macro-fuga (x)
`(+ ,@x))
> (macroexpand-1 '(macro-fuga (1 2 3 4)))
(+ 1 2 3 4) ;
T
以下のようなマクロがあったとする.
> (defmacro macro-foo (x)
      `(let ((value 0))
        ,@x
        (format t "value = ~A~%" value)))
MACRO-FOO
> (macro-foo (+ 1 1))
value = 0
NIL
このマクロは引数にかかわらずvalueを0に設定し,出力するものである.だが,マクロへの引数をvalueに値を設定する式にすると予期せぬ結果となる.
> (macro-foo ((setf value 1)))
value = 1
NIL
> (macroexpand-1 '(macro-foo ((setf value 1))))
(LET ((VALUE 0))
     (SETF VALUE 1)
     (FORMAT T "value = ~A~%" VALUE)) ;
T
何が起こっているかはmacroexpand-1の出力ではっきりする.マクロは式を変換するだけなので,このようなことがおこる(意図しない変数キャプチャ).もちろん意図してこのようなマクロを作成する場合もありうるが,多くの場合このような現象は避けたい.そのためにはマクロ内で使用する変数をインターンしないgensymというものを使用する.
> (defmacro macro-foo (x)            
      (let ((value (gensym)))
       `(progn (setf ,value 0)
         ,@x
         (format t "value = ~A~%" ,value))))
MACRO-FOO
> (macro-foo ((setf value 100)))
value = 0
NIL
> (macroexpand-1 '(macro-foo ((setf value 100))))
(PROGN (SETF #:G10566 0) (SETF VALUE 100) (FORMAT T "value = ~A~%" #:G10566)) ;
T
gensymのシンボルはインターンされない.で,macroexpand-1の出力のように#:G10566(※)といったもので示されており,同出力から変数キャプチャは回避されている.マクロではgensymのシンボルを参照するためにバッククォートとコンマにつづくシンボル名を使用する.gensymは予期しない変数キャプチャだけでなく,マクロの引数の多重評価を回避するためにも使用される.
※ 今回たまたまこのような値になっただけで,処理系や実行したタイミングで異なる.
matsu(C)
Since 2002
Mail to matsu