| [ < ] | [ > ] | [ << ] | [上] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
マクロ(macros)により, 新たな制御構造の構文を定義したり, 他の言語の機能を定義したりできます. マクロは関数のように定義しますが, 値の計算方法を指示するかわりに, 値を計算するための別のLisp式の計算方法を指示します. この式をマクロの展開形(expansion)と呼びます.
マクロでこのようなことができるのは, 関数が評価済みの引数を操作するのに対して, マクロは引数の未評価の式を操作するからです. そのため, これらの引数の式やその一部を含む展開形を構築できるのです.
実行速度のために普通の関数でできることにマクロを使うのであれば, そのかわりにインライン関数を使うことを考えてください.
| 12.1 マクロの簡単な例 | A basic example. | |
| 12.2 マクロ呼び出しの展開 | How, when and why macros are expanded. | |
| 12.3 マクロとバイトコンパイル | How macros are expanded by the compiler. | |
| 12.4 マクロ定義 | How to write a macro definition. | |
| 12.5 バッククォート | Easier construction of list structure. | |
| 12.6 マクロ使用時の一般的な問題 | Don’t evaluate the macro arguments too many times. Don’t hide the user’s variables. |
| [ < ] | [ > ] | [ << ] | [上] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
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を増やします.
| [ < ] | [ > ] | [ << ] | [上] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
マクロ呼び出しはマクロ名で始まるリストであり, 関数呼び出しとほとんど同じに見えます. リストの残りの要素はマクロの引数です.
マクロ呼び出しの評価は, 関数呼び出しの評価のように始められますが, 1つだけ重要な違いがあります. マクロの引数は, マクロ呼び出しに現れた実際の引数です. マクロ定義に渡すまえに, それらを評価しません. 一方, 関数の引数は, 関数呼び出しのリストの要素を評価した結果です.
引数を得ると, Lispは関数定義を起動するのと同様にマクロ定義を起動します.
マクロの引数変数は, マクロ呼び出しの引数値や
&rest引数の場合にはそれらのリストに束縛されます.
そうして, マクロ本体を実行し, 関数本体と同様に値を返します.
マクロと関数の重要な違いの2つめは, マクロ本体が返した値はマクロ呼び出しの値ではないことです. 戻り値は値を計算するためのかわりの式であり, これをマクロの展開形(expansion)といいます. Lispインタープリタは, マクロから戻ってくると, ただちに展開形を評価することへ進みます.
展開形は, 通常どおりに評価されるので, 展開形から他のマクロを呼び出してもかまいません. 同一のマクロを呼び出してもかまいませんが, それは一般的ではありません.
macroexpandを呼ぶと, 指定したマクロの展開形を調べることができます.
この関数は, formがマクロ呼び出しならば, それを展開する.
その結果がまた別のマクロ呼び出しであれば, さらに展開する.
マクロ呼び出しでない結果を得るまでこれを繰り返す.
それが, macroexpandが返す値である.
formが始めからマクロ呼び出しでなければ,
与えられたとおりのものを返す.
macroexpandはformの部分式を調べないことに注意してほしい
(ただし, マクロ定義によっては調べるかもしれない).
部分式がマクロ呼び出しであったとしても,
macroexpandはそれらを展開しない.
関数macroexpandは, インライン関数の呼び出しは展開しない.
インライン関数の呼び出しを理解することは普通の関数呼び出しを理解するのと
かわりないので, 通常, そのような展開を行う必要はない.
environmentを指定すると, それは, 現在定義済みのマクロを隠すマクロ定義の連想リストを表す. バイトコンパイルではこの機能を使う.
(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)) ; ここでは |
| [ < ] | [ > ] | [ << ] | [上] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
なぜ, マクロの展開形をわざわざ計算してから展開形を評価するのか,
疑問に思うかもしれません.
なぜ, マクロ本体で望みの結果を直接出さないのでしょう?
その理由には, コンパイルが関係しています.
コンパイルするLispプログラムにマクロ呼び出しが現れると, Lispコンパイラは, インタープリタがするのと同様にマクロ定義を呼び出し, その展開形を受け取ります. この展開形を評価するかわりに, コンパイラは, 展開形がプログラムに直接現れたかのようにそれをコンパイルします. その結果, コンパイル済みのコードは, マクロが意図した値と副作用を生じ, かつ, 実行速度はコンパイルした速度になるのです. マクロ本体そのもので値と副作用を計算したのでは, このように動作しません. コンパイル時に計算してしまい, それでは意味がありません.
マクロ呼び出しが正しくコンパイルされるためには,
それらの呼び出しをコンパイルするときに,
Lisp内でマクロが定義済みである必要があります.
コンパイラには, 読者がこのようにすることを補佐する機能があります.
コンパイル対象のファイルにフォームdefmacroが含まれていると,
そのファイルの残りをコンパイルするあいだは,
一時的にマクロを定義します.
この機能が動作するためには,
defmacroを同じファイルの最初に利用する箇所よりまえに
入れておく必要があります.
ファイルをバイトコンパイルすると,
そのファイルのトップレベルにあるrequireの呼び出しを実行します.
これは, ファイルを正しくコンパイルするために必要なパッケージを表します.
コンパイル中に必要なマクロ定義が使えることを保証する1つの方法は,
それらのマクロを定義するファイルを
requireに指定しておくことです(see section 機能).
コンパイル済みのプログラムを実行するときに,
マクロを定義したファイルをロードしてしまうことを避けるには,
requireの呼び出しの周りにeval-when-compileを書いておきます
(see section コンパイル時の評価).
| [ < ] | [ > ] | [ << ] | [上] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
Lispのマクロは, そのCARがmacroであるリストです.
そのCDRは関数であるべきです.
マクロの展開は, マクロ呼び出しの未評価の引数式に
(applyで)関数を適用して動作します.
無名関数のように無名Lispマクロを使うことも可能ですが,
けっしてしないでしょう.
mapcarのようなファンクショナルに無名マクロを渡す意味がないからです.
実用上は, すべてのLispマクロには名前があり,
普通, スペシャルフォームdefmacroで定義します.
defmacroは, シンボルnameをつぎのようなマクロとして定義する.
(macro lambda argument-list . body-forms) |
(このリストのCDRは関数, つまり, ラムダ式であることに注意. )
このマクロオブジェクトは, nameの関数セルに格納される.
フォームdefmacroを評価した結果, 返される値はnameであるが,
通常この値は無視する.
argument-listの形式と意味は, 関数のそれと同じであり,
キーワード&restや&optionalを使ってもよい
(see section 引数リストのその他の機能).
マクロにも説明文字列を指定できるが,
マクロを対話的に呼び出すことはできないので,
interactive宣言は無視する.
| [ < ] | [ > ] | [ << ] | [上] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
マクロでは, 定数部分と非定数部分を組み合わせた大きなリスト構造を 構築する必要がしばしばあります. これを簡単に行うためには, (通常, バッククォート(backquote)と呼ばれる)‘`’構文を 使います.
バッククォートにより, リストの要素を選択に評価しつつ,
リストをクォートできます.
もっとも単純な場合, これはスペシャルフォームquote(see section クォート)と
等価です.
たとえば, つぎの2つのフォームは等価な結果になります.
`(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)
|
‘,’による置き換えは, リスト構造の深いレベルでも許されます. たとえば, つぎのとおりです.
(defmacro t-becomes-nil (variable)
`(if (eq ,variable t)
(setq ,variable nil)))
(t-becomes-nil foo)
≡ (if (eq foo t) (setq foo nil))
|
特別な印‘,@’を使って, 評価結果を結果となるリストに繋ぎ合わせる(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の旧版では, ‘`’の構文は異なっていて, バッククォート構文全体を囲む括弧の余分なレベルが必要でした. 同様に, ‘,’や‘,@’の置換でも, ‘,’や‘,@’, および後続の式を囲む括弧の余分なレベルが1つ必要でした. 古い構文では, ‘`’, ‘,’, ‘,@’と後続の式とのあいだには 空白が必要でした.
この構文も受け付けますが, これはEmacsの旧版との互換性のためであり, 新しいプログラムでは使わないことを勧めます.
| [ < ] | [ > ] | [ << ] | [上] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
マクロ展開に関する基本的事実には, 直観的でない結果があります. 本節では, 問題を引き起こしかねない重要な結果を説明し, 問題を回避するための規則を説明します.
| 12.6.1 マクロ引数の複数回評価 | The expansion should evaluate each macro arg once. | |
| 12.6.2 マクロ展開形内のローカル変数 | Local variable bindings in the expansion require special care. | |
| 12.6.3 展開形におけるマクロ引数の評価 | Don’t evaluate them; put them in the expansion. | |
| 12.6.4 マクロは何回展開されるか | Avoid depending on how many times expansion is done. |
| [ < ] | [ > ] | [ << ] | [上] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
マクロを定義するときには, 展開形を実行するときに, 引数が何回評価かされるかに注意を払う必要があります. つぎの(繰り返しを行う)マクロで, この問題を示しましょう. このマクロで, 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
|
このマクロの引数, from, to, doは,
『シンタックスシュガー』であり, 完全に無視します.
(from, to, doなどの)余分な単語を
マクロ呼び出しのこの引数位置に書けるようにするのです.
バッククォートを使って単純化した等価な定義をつぎに示します.
(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に副作用があると, 複数回評価するのは正しくありません.
繰り返し評価することがマクロの意図している目的の一部でなければ, よく設計されたマクロ定義では, 引数をちょうど1回だけ評価するような展開形を生成して, 上のような問題を回避するように手立てします.
(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))))
|
残念なことに, この修正は, 次節に説明する別の問題を引き起こします.
| [ < ] | [ > ] | [ << ] | [上] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
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のかわりに,
インターンしてないシンボル(see section シンボルの作成とインターン)を使います.
インターンしてないシンボルは, 他のシンボルと同様に,
束縛したり参照したりできますが, forで作ったので,
ユーザープログラムには現れていないことがわかっています.
インターンしてないので, ユーザーがプログラムのあとの部分で
参照する方法もありません.
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という名前のインターンしてないシンボルを作成し,
もとの式に現れていたインターンしたシンボルmaxのかわりに
展開形内部で使います.
| [ < ] | [ > ] | [ << ] | [上] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
eval(see section 評価(eval))を呼び出すなどして,
マクロ定義そのものの中でマクロ引数の式を評価すると,
別の問題を生じます.
引数でユーザーの変数を参照する場合,
ユーザーがマクロ引数の1つと同じ名前を使っていると,
問題になります.
マクロ本体の内側では, マクロ引数の束縛が最ローカルな束縛ですから,
そのフォームの内側からの参照は, この束縛を使います.
例を示しましょう.
(defmacro foo (a)
(list 'setq (eval a) t))
⇒ foo
(setq x 'b)
(foo x) → (setq b t)
⇒ t ; |
ユーザーの引数の名前がaかxかで違いがでます.
というのは, マクロ引数の変数aとaが衝突するからです.
マクロ定義内でevalを呼び出したときの別の問題点は,
コンパイルしたプログラムでは, 意図した動作をしないだろうということです.
バイトコンパイラは, プログラムをコンパイル中にマクロ定義を実行しますから,
(evalで参照したい)プログラムそのものの計算は行われず,
そのローカル変数の束縛も存在しません.
これらの問題を回避するには, マクロ展開の計算過程では, 引数の式を評価しないことです. そのかわりに, マクロ展開では式の置換を使って, 展開時にその値が計算されるようにします. このようにすれば, 本章の他の例題は動作します.
| [ << ] | [ >> ] | [冒頭] | [目次] | [見出し] | [ ? ] |
この文書は新堂 安孝によって2009年9月22日にtexi2html 1.82を用いて生成されました。