[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

12. マクロ

マクロ(macro)を使うと 新しい制御構文要素などの言語機能を定義することができます。 マクロは関数と同じように定義できますが、 値を計算する方法を定義するのではなく、 値を計算するための別のLisp式を計算する方法を定義します。 このLisp式をマクロの展開(expansion)といいます。

マクロでこのようなことができるのは、 関数のように引数の値を処理するのではなく、 未評価の引数式を処理するからです。 そのため、 未評価の引数式やその一部を含んだ式を展開させることができます。

通常の関数でできることを、効率だけを考えてマクロを使っているのであれば、 かわりに インライン関数を使うことを検討してください。See section インライン関数


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

12.1 簡単なマクロの例

Cの++演算子のような、 変数値をインクリメントするLisp構文要素を定義したい場合を考えてみてください。 (inc x)と書いて、 (setq x (1+ x))の効果をもたせたいわけです。 これを行なうためのマクロを以下に示します。

 
(defmacro inc (var)
   (list 'setq var (list '1+ var)))

これが(inc x)のように呼ばれると、 引数varの値は、 xではなくxとなります。 マクロの本体は展開、 すなわち(setq x (1+ x))を構築するのにこれを使います。 マクロ定義がこの展開を返すと、 この式をLispが評価し、 変数xがインクリメントされます。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

12.2 マクロ呼出しの展開

マクロ呼出しは、 マクロの名前で始まるリストである点で、 ちょうど関数呼出しと似ています。 リストの残りの要素はマクロの引数です。

マクロ呼出しの評価は、 関数呼出しの評価のように始まりますが、 一つだけ決定的に違う点があります。 それは、 マクロ引数はマクロ呼出しに現われる式そのものであることです。 その式はマクロ定義に渡される前には評価されません。 逆に、 関数の引数には 関数呼出しのリストの要素を評価した結果が渡されます。

引数を得ると、 Lispは関数を起動するようにマクロ定義を起動します。 マクロの仮引数はマクロ呼出しの引数値に束縛されます。 ただし、&rest引数のあるときは、 マクロ呼出しの引数値の リストに束縛されます。 ちょうど関数本体が実行をして値を返すように、 マクロ本体も実行をして値を返します。

マクロと関数の決定的な違いの二つめは、 マクロ本体の返す 値はマクロ呼出しの値ではないことです。 そのかわりにマクロ本体の返す値は、 マクロ呼出しの値を計算するための別な式で、 マクロ展開(expansion)といいます。 Lispインタプリタは、 マクロから帰ると、すぐに返された式を評価します。

展開された式は、通常の方法で評価されるため、 別のマクロ呼出しを含んでいてもかまいません。 普通は使われませんが、 再帰的なマクロ呼出しも誤り ではありません。

macroexpandを使うと、 引数に与えたマクロ呼出しの展開を見ることができます。

Function: macroexpand form &optional environment

この関数は、 formがマクロの場合、それを展開します。 結果が別なマクロ呼出しの場合、 それはマクロ呼出しでない何かになるまで、次々に展開されます。 展開した結果が macroexpandの戻り値です。 formがマクロ呼出しでなければそれをそのまま返します。

macroexpandは、 formの部分式(訳注: formへの引数)を見ません (そういうものを見るマクロ定義はあるかもしれませんが)。 たとえそれがマクロ呼出しであっても、 macroexpandはそういうものを展開しません。

関数macroexpandは、 インライン関数の呼出しを展開しません。 インライン関数は通常の関数にくらべても理解するのはそれほど難しくないので、 普通はその必要がありません。

environmentを使って 現在定義されているマクロをシャドウするマクロ定義のalistを指定することも できます。 バイトコンパイルでこの機能を使います。

 
(defmacro inc (var)
    (list 'setq var (list '1+ var)))
     ⇒ inc

(macroexpand '(inc r))
     ⇒ (setq r (1+ r))

(defmacro inc2 (var1 var2)
    (list 'progn (list 'inc var1) (list 'inc var2)))
     ⇒ inc2

(macroexpand '(inc2 r s))
     ⇒ (progn (inc r) (inc s))  ; ここでは、incは展開されない。

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

12.3 マクロとバイトコンパイル

わざわざマクロの展開を計算してから展開を評価するのはどうしてなのか、 不思議に思うかもしれません。 マクロ本体が必要な結果を直接つくらないのはどうしてなのか? そのわけはコンパイルに関係があります。

コンパイルされるLispプログラムの中にマクロ呼出しがあると、 Lispコンパイラはインタプリタのようにマクロ定義を呼んで、 マクロが展開をした結果を受け取ります。 しかし、展開された式を評価するのではなく、 展開されたコードがプログラム中に直接現われたかのように、 コンパイルを行ないます。 結果として、 コンパイルされたコードは、 マクロが想定した値を返すと同時に副作用を生み出しますが、 実行速度が損なわれることはありません。 もしマクロ本体が戻り値を計算したり、 副作用自身を起したりするとしたならば、 マクロはうまく動作しません。 そのような場合、マクロはコンパイル時に計算されるため、 便利ではないでしょう。

コンパイルしたマクロ呼出しがうまくはたらくためには、 マクロへ の呼出しがコンパイルされるときには、 あらかじめLispに定義されていなければなりません。 コンパイラにはこれを助ける特別な機能があります。 コンパイルされるファイルにdefmacroがあるときには、 ファイルの残りの部分をコンパイルしている間、 一時的にこのマクロが定義されます。 この機能を使うためには、 マクロを使うのと同じファイルで マクロを使う前に定義する必要があります。

ファイルのバイトコンパイル時には ファイルのトップレベルにあるrequire呼出しをすべて実行します。 これはファイルを正しくコンパイルするために、 requireされたパッケージが必要な場合があるためです(see section 機能)。 コンパイル中に必要なマクロ定義を有効にする方法の一つに、 それらを定義するファイルをrequireするという方法があります。 コンパイルしたプログラムを誰かが作動させたとき、 マクロの定義ファイルのロードをさせずにすますには、 require呼出しのまわりにeval-when-compileを書きます (see section コンパイル時に行なわれる評価)。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

12.4 マクロの定義

LispマクロとはCARmacroのリストのことです。 CDRは関数でなければなりません。 マクロの展開は、 マクロ呼出しへの未評価の引数式のリストに、 その関数 (applyで) 適用して得ます。

匿名の関数のように、 匿名のLispマクロというのも あり得ますが、 実際には使われません。 というのも、 mapcarのようなファンクショナルに匿名マクロを渡しても 意味がないためです。 実際のところ、 すべてのLispマクロが名前をもっていて、 通常defmacro特殊形式で定義されます。

Special Form: defmacro name argument-list body-forms…

defmacroは、 nameを以下のようなマクロとして定義します。

 
(macro lambda argument-list . body-forms)

nameの関数セルにこのマクロ・オブジェクトを 格納します。 defmacro形式を評価したとき、戻される値はnameです。 しかし、多くの場合はこの値を無視します。

argument-listの形式と意味は関数の場合と同じです。 キーワード&rest&optionalも使えます (see section 引数リストの高度な機能)。 マクロに説明文字列はあってもかまいませんが、 マクロを対話的に呼ぶことができないため、 interactive宣言は無視されます。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

12.5 逆引用符

マクロを使って、定数と非定数の部分が混ぜたものから 大きなリスト構造を構築する必要があることがよくあります。 これを書くのをたやすくするため、 マクロ``' (逆引用符(backquote)と呼ぶことが多い) を使います。

逆引用符ではリストをquoteしますが、さらに、 リストの要素を選択的に評価することができます。 最も単純な使い方をした場合、 逆引用符は特殊形式quoteと同じはたらきをします (see section quoteする)。 たとえば、以下に示す二つの形式は、 同じ結果になります。

 
`(a list of (+ 2 3) elements)
     ⇒ (a list of (+ 2 3) elements)
'(a list of (+ 2 3) elements)
     ⇒ (a list of (+ 2 3) elements)

逆引用符の引数の中に特別な標識`,'を入れると、 その値は定数でないことを表わします。 逆引用符は`,'の引数を評価し、 リスト構造にその値を入れます。

 
(list 'a 'list 'of (+ 2 3) 'elements)
     ⇒ (a list of 5 elements)
`(a list of ,(+ 2 3) elements)
     ⇒ (a list of 5 elements)

特別な標識`,@'を用いることで、 評価した値を結果のリストに組み込む(splice)こともできます。 組み込まれたリストの要素は、 結果のリストのほかの要素と同じレベルの要素になります。 ``'を使わない等価なコードは、 往々にして読みにくくなります。 例をあげます。

 
(setq some-list '(2 3))
     ⇒ (2 3)
(cons 1 (append some-list '(4) some-list))
     ⇒ (1 2 3 4 2 3)
`(1 ,@some-list 4 ,@some-list)
     ⇒ (1 2 3 4 2 3)

(setq list '(hack foo bar))
     ⇒ (hack foo bar)
(cons 'use
  (cons 'the
    (cons 'words (append (cdr list) '(as elements)))))
     ⇒ (use the words foo bar as elements)
`(use the words ,@(cdr list) as elements)
     ⇒ (use the words foo bar as elements)

バージョン19.29より前のEmacsの``'では、 逆引用符構文要素全体のまわりに余分な括弧を必要とする異なった構文を使っていました。 同様に、各`,'`,@'置換も`,'`,@'と それに後続する式のまわりに余分な括弧を必要としていました。 旧構文では``'`,'`,@'と後続する式の間に、 空白が必要でした。

旧構文も受けつけられますが、 旧バージョンのEmacsとの互換性を例外にすれば、 これはもはや推奨されません。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

12.6 マクロの利用でよく見られる問題点

マクロの展開は直感的でない結果を引き起こす、 という基本的事実があります。 この節では、 トラブルを引き起こす重要な結果と、 トラブルを避けるためのルールについて述べます。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

12.6.1 マクロ引数の反復的評価

マクロを定義する際は、 展開が実行されるときに、 引数が評価される回数について 気をつけないといけません。 (これを使うと繰返しに便利な) 次のマクロで問題を説明します。 このマクロは、 Pascalに見られるような単純な"for"ループを書けるようにし ます。

 
(defmacro for (var from init to final do &rest body)
  "Execute a simple \"for\" loop.
For example, (for i from 1 to 10 do (print i))."
  (list 'let (list (list var init))
        (cons 'while (cons (list '<= var final)
                           (append body (list (list 'inc var)))))))
⇒ for

(for i from 1 to 3 do
   (setq square (* i i))
   (princ (format "\n%d %d" i square)))
→
(let ((i 1))
  (while (<= i 3)
    (setq square (* i i))
    (princ (format "%d      %d" i square))
    (inc i)))

     -|1       1
     -|2       4
     -|3       9
⇒ nil

(マクロの引数fromtodoは「シンタックス・シュガー」"syntactic sugar" であり、すべて無視されます。 これはマクロの呼出し時に(fromtodoのような)余計な単語 (noise words)を、 その引数の位置 に書くことができるようにするための工夫です。)

逆引用符を使用して単純にした等価な定義を示します。

 
(defmacro for (var from init to final do &rest body)
  "Execute a simple \"for\" loop.
For example, (for i from 1 to 10 do (print i))."
  `(let ((,var ,init))
     (while (<= ,var ,final)
       ,@body
       (inc ,var))))

形式の定義の両方とも (逆引用符の有無にかかわらず)、 繰り返しごとにfinalを評価するという欠点をもっています。 finalが定数であれば問題はありません。 より複雑な形式の場合、 たとえば、(long-complex-calculation x)のような場合、 これはかなり実行を遅らせてしまう 可能性があります。 もしfinalが副作用をもつなら、 2回以上実行するのは多分誤りです。

評価の繰り返し がマクロの意図した目的である場合を除き、 引数式を正確に一度だけ評価するような展開を生成することで、 よくできたマクロ定義はこの問題を回避しています。 forマクロにたいする正しい展開を以下に示します。

 
(let ((i 1)
      (max 3))
  (while (<= i max)
    (setq square (* i i))
    (princ (format "%d      %d" i square))
    (inc i)))

この展開を生成するマクロ定義です。

 
(defmacro for (var from init to final do &rest body)
  "Execute a simple for loop: (for i from 1 to 10 do (print i))."
  `(let ((,var ,init)
         (max ,final))
     (while (<= ,var max)
       ,@body
       (inc ,var))))

残念ながら、 これによりまた、別の問題が発生します。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

12.6.2 マクロ展開内の局所変数

新しいforの定義には、新しい問題があります。 ユーザが予期しないmaxという局所変数を導入しています。 そのため以下に示す例でトラブルが発生します。

 
(let ((max 0))
  (for x from 0 to 10 do
    (let ((this (frob x)))
      (if (< max this)
          (setq max this)))))

for本体の中からのmaxへの参照は、 ユーザによるmaxの束縛を参照することが想定されますが、 実際はforによる束縛をアクセスします。

これを直すには、 maxのかわりにinternされていない(uninternされている)シンボルを使います (see section シンボルの作成と intern)。 internされてない(uninternされている)シンボルは、 ほかのシンボルのように束縛、 参照できますが、 forがこれを作ったので、 ユーザのプログラムにもとから現われていることはありえません。 internされてない(uninternされている)ので、 ユーザが後でプログラムの中に入れる方法はありません。 forによって置かれた場所以外でそれが現われることはありません。 このようにはたらくforの定義を示します。

 
(defmacro for (var from init to final do &rest body)
  "Execute a simple for loop: (for i from 1 to 10 do (print i))."
  (let ((tempvar (make-symbol "max")))
    `(let ((,var ,init)
           (,tempvar ,final))
       (while (<= ,var ,tempvar)
         ,@body
         (inc ,var)))))

通常の展開に現われるmaxというinternされたシンボルのかわりに、 maxというinternされてない(uninternされている)シンボルを作り、 展開に入れています。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

12.6.3 展開内のマクロ引数の評価

展開を計算しているときにeval (see section Eval) を呼ぶなどしてマクロ引数式を評価すると、 別な問題がおこります。 もし引数がユーザの変数を参照しようとすると、 ユーザがたまたまマクロ引数の一つと同じ名前の変数を使おうとするときに、 トラブルになります。 マクロ本体の内側では、 マクロ引数の束縛がこの変数のもっとも局所的な束縛になるので、 評価されている形式の内側の参照は、 それを参照します。 例をあげます。

 
(defmacro foo (a)
  (list 'setq (eval a) t))
     ⇒ foo
(setq x 'b)
(foo x) → (setq b t)
     ⇒ t                  ; でbを設定した。
;; ところが
(setq a 'c)
(foo a) → (setq a t)
     ⇒ t                  ; しかしこれはcでなく
;; aのセットをした。

ユーザの変数がaという名かxという名かによって違いがでます。 aはマクロの仮引数のaと衝突しているからです。

マクロ定義でevalを呼ばないもう一つの理由は、 プログラムをコンパイルすると おそらくあなたの思うとおりにはうごかないだろうということです。 バイトコンパイラはプログラムのコンパイル中にマクロ定義を実行しますが、 この時点では (evalを通じてアクセスしようとした) プログラム中で行なわれるはずの計算が行なわれないため、 ローカルな変数への束縛は行なわれていません。

式の実行時における値で安全に動作させるためには マクロ展開に式を代入してください。 そうすると、 展開の実行の一部として値が計算されます。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

12.6.4 何回マクロが展開されるか?

インタープリトされる関数では、 マクロは評価されるたびにマクロ呼出しが展開されるのにたいし、 コンパイルされる関数では (コンパイル時に) 1回だけ展開されるため、 問題が発生することがあります。 マクロ定義が副作用をもつなら、 マクロが何回展開されるかによって 動作が異なってきます。

とりわけオブジェクトを構築するのは副作用の一つです。 マクロが1回呼び出されるとオブジェクトは1回だけ構築されます。 いいかえると、 マクロが実行されるたびに同じオブジェクトの構造が使われます。 インタープリトされた操作では、 マクロは毎回展開されマクロは毎回新しいオブジェクトの集まりを生成します。 通常はそれで何の問題も起こりません。 オブジェクトは共有されるかいなかにかかわらず、 同じ内容をもっています。 しかし、もし前後のプログラムがオブジェクトに副作用をあたえるならば、 共有されているかどうかによって動作が異なってきます。 例をあげます。

 
(defmacro empty-object ()
  (list 'quote (cons nil nil)))

(defun initialize (condition)
  (let ((object (empty-object)))
    (if condition
        (setcar object condition))
    object))

initializeがインタープリトされるなら、 新しいリスト(nil)が、initializeが呼び出されるたびに構築されます。 よって、 複数の呼出しの間に副作用が残ることはありません。 initializeがコンパイルされるなら、 マクロempty-objectはコンパイル中に展開され、 一つの「定数」(nil)を生成し、 initializeが呼ばれるごとに再利用されます。

このような病的な例を避ける一つの方法は、 empty-objectをメモリに割りつける構文要素ではなく、 おもしろい種類の定数とみなすことです。 '(nil)のような定数にsetcarを使うことはないでしょう。 そうすればempty-objectでそれを使うことも当然ないでしょう。


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Yasutaka SHINDOH on September, 29 2006 using texi2html 1.76.