いつマクロを使うべきか

ある関数が,本当にマクロであるよりも関数であった方が良いというのは, どうしたら分かるだろうか? マクロが必要な場合とそうでない場合の間には,大抵明確な区別がある. 基本的には関数を使うべきだ: 関数で間に合う所にマクロを使うのはエレガントでない. マクロが何か特定の利益を与えるときのみそれを使うべきなのだ.

マクロが利益を与えるときとはいつだろうか? それがこの章の主題だ. 普通この疑問は,改良を目指すときでなく必要に迫られた上で発せられる. マクロで実現できることの大部分は,関数ではできない. 第8.1節ではマクロとしてのみ実装できるオペレータの種類を列挙する. だが境界線上に立つような場合もわずかにある(しかしこれは興味深い). つまりオペレータを関数で書いてもマクロで書いてもよいような状況だ. そのような状況について,第8.2節ではマクロについての賛否両論を挙げる. 最後に,マクロに何ができるのかを考慮した上で, 第8.3節では関連した疑問について考える. 人はマクロでどんなことを行うものなのだろうか?

他に何も関係しないとき

プログラム内の数個所に似通ったコードが表れているとき,サブルーチンを書き, それらをサブルーチン呼出しで置き換えるのは,よいデザインのための一般原則だ. この原則をLispプログラムに適用するとき, 「サブルーチン」を関数にすべきかマクロにすべきか決めなければならない.

関数ではなくマクロを書こうと用意に決心できることがある. マクロのみが必要なことを実現できるときだ. 1+ のような関数は,関数としてもマクロとしても書けるかもしれない.

(defun 1+ (x) (+ 1 x))

(defmacro 1+ (x) `(+ 1 ,x))

しかし第7.3節のwhileは,マクロでないと定義できない:

(defmacro while (test &body body)
  `(do ()
     ((not ,test))
     ,@body))

このマクロの動作を関数で真似る方法はない. whileの定義では,本体として渡された式をdoの本体内に張り込むようになっているが, これはテスト式がnilを返したときのみ評価される. どんな関数にもそれは無理だ: 関数呼び出しでは,全ての引数は関数の呼び出しよりも前に評価されるからだ.

確かにマクロが必要というとき,マクロに何を求めているのだろうか? マクロにできて関数にできないことは2つある: マクロは引数の評価を制御(または抑制)でき, また呼び出し側のソースコードそのものへと展開される. 結局,マクロが必要なアプリケーションには,これらの性質の片方または両方を必要なのだ.

簡単に説明するときの「マクロは引数を評価しない」というのは少し間違っていて, こう言うと正確になる. マクロはマクロ呼び出し内の引数の評価を制御する. 評価回数は引数がマクロの展開コードのどこに置かれるかによるが, 1回でも複数回でも良いし,全く評価しないこともある. マクロでのこの制御の使われ方は主に次の4通りだ:

  1. 変形. Common Lispのマクロsetfは引数を評価前に取り出す類のものだ. 組み込みのアクセス関数には,しばしば逆の動作を行う関数がある. その目的はアクセス関数が取ってきたものを設定することだ. carについてはrplacaofcdrrplacd等が当てはまる。 setfを使うと,そういったアクセス関数を設定されるべき変数と同じように呼び出せる. 例えば(setf (car x) 'a)(progn (rplaca x 'a) 'a)に展開され得る.

    この技を実現するには,setfはその第1引数の内部を見なければならない. 上の場合でrplacaが必要だと知るためには, setfには第1引数がcarで始まる式であることが見えなければならない. よってsetfを始めとする,引数の変形を行うオペレータは, どれもマクロとして書かなければならない.

  2. 変数束縛. レキシカル変数はソースコード内に直接書かれなければならない. 例えばsetqの第1引数は評価されないので、 setqの上に構築されたものは全てsetqへと展開されるマクロでなければならない. setqを呼び出す関数ではだめだ. それは引数がλ式の仮引数として現れるletなどのオペレータや, letsへと展開されるdo等のマクロについても同様だ。 引数のレキシカルな束縛を変更するための新オペレータは, いずれもマクロとして書かれなければならない。
  3. 条件分岐による評価. 関数の引数は全て評価される. whenのような制御構文では,ある引数は特定の条件下でのみ評価される. そのような柔軟性はマクロでのみ得られる.
  4. 複数回の評価. 関数の引数は全て評価されるだけでなく,きっかり1回評価される. マクロはdoのような制御構文の定義に使われるが,この場合, ある引数は繰り返し評価されなければならない.

マクロのインライン展開を活用する方法もいくつかある。 そこで展開形は例えばマクロ呼び出しのレキシカルコンテキスト内に現れることを強調したい. なぜなら3通りのうち2通りのマクロがその事実に依拠しているからだ. すなわち、

  1. 呼び出し側環境を利用する. マクロは,呼び出し側のコンテキストに束縛された変数を含む展開形を生成できる. 下のマクロの動作は,fooが呼ばれた場所でのyの束縛に依存する.
    (defmacro foo (x)
      `(+ ,x y))
    
    普通この類のレキシカルな結び付きは喜びの源というよりも伝染病の感染源として見なされる. 通常,そのようなマクロを書くのは悪いスタイルだ. 関数プログラミングの理想はマクロにも同様に適用される: マクロは引数を通じて相互作用することが望ましい. 実際,呼出側環境を利用することは滅多に必要にならず, そのような状況が起きたとしても,それは大抵誤りによるものだ(第9章を参照). この本の中の全てのマクロのうち,継続を渡すマクロ(第20章)とATNコンパイラ(第23章)のみが その方法で呼び出し側環境を利用している.
  2. 新しい環境を包み込む. マクロは引数を新たなレキシカル環境で評価させることもできる. 古典的な例はletで,lambdaの上にマクロを被せることで実装できる(penpenページ). (let ((y 2)) (+ x y))等の式の実行部本体の中では, yは新しい変数を指すことになる.
  3. 関数呼び出しを節約する. マクロの展開結果をインライン挿入することの3つ目の結果は, コンパイル後のコードではマクロ呼び出しに関わるオーヴァヘッドが一切ないことだ. 実行時には,マクロ呼び出しは展開形に置き換え済みになっている. (原則的にはインライン宣言された関数でも同じことだが)

驚いたことに第5番目と第6番目の用法は, 意図せずに使われたときは,変数捕捉の問題を引き起こす. これはマクロを書くときに恐らく一番恐ろしいことだ. 変数捕捉については第9章で論じる.

マクロの利用法には7通りあると言う代わりに,6通り半あると言った方がよいだろう. 理想の世界では全てのCommon Lispコンパイラがインライン宣言に従い, 関数呼び出しの節約はマクロではなくインライン関数の仕事になっていることだろう. 理想の世界は読者への課題として残しておく.

マクロと関数どちらがよい?

前節では判断の容易なケースを扱った。 引数に、評価前にアクセスするオペレータは全てマクロで書かなければならない。 他に選択肢はないからだ。 それではマクロでも関数でも書けるようなオペレータはどうだろうか? 例えば引数の平均値を返すオペレータavgを考えてみよう. 次のように関数として書くことができる.

(defun avg (&rest args)
  (/ (apply #'+ args) (length args)))

しかし次のようにマクロとして書いた方がよい.

(defmacro avg (&rest args)
  `(/ (+ ,@args) ,(length args)))

関数版avgは呼ばれる度にlengthを呼ぶが,それは不必要だ. コンパイル時には引数の値までは不明かもしれないが,それが幾つあるかは分かる. よってlengthの呼び出しはそのときに済ませた方がよい. 似たような選択を迫られたときに考慮すべき点は以下の通りだ.

マクロの長所

  1. コンパイル時の計算. マクロ呼び出しには,2つの時点での処理が関わってくる. マクロの展開時と,展開形の評価時だ. Lispプログラムでのマクロ展開は全てプログラムがコンパイルされたときに行われ, コンパイル時に実行できるどのような計算も,実行時にプログラムを遅くすることはない. オペレータが仕事の一部をマクロ展開の段階で済ませるように書けるなら, マクロとして書く方が効率がよいだろう. 賢いコンパイラでもできないようなことがあれば, 結局実行時に関数がこなすことになるのだから. 第13章ではavgのように展開時に仕事を行うマクロについて説明する.
  2. Lispとの緊密な連携. 関数ではなくマクロを使うと,プログラムをLispと緊密に連携させられることがある. ある問題を解くプログラムを書く代わりに, マクロを使ってその問題をLispが既に解法を知っているような問題へ変形できるときがある. このアプローチが可能なときは,大抵プログラムは小さくかつ効率的になる: Lispが処理を代わりに行ってくれるので小さくなり, Lisp処理系の製品は一般的にユーザのプログラムより無駄を削いであるから効率がよくなる,という訳だ. この長所は主に埋め込み言語で明らかになるが,それについての説明は第19章から始まる.
  3. 関数呼び出しの節約. マクロ呼び出しは,書かれた所に直接展開される. だからよく使われるコードのまとまりをマクロとして定義しておけば, それが使われる度に関数を呼び出さなくともよい. Lispの古い方言では,プログラマは実行時の関数呼び出しを節約するために, マクロのこの性質を活用していた. Common Lispでは,この仕事はインライン宣言された関数が代わりに行うものとされている. 関数をインライン宣言することで, コンパイルされたら呼出側コードの中に埋め込まれるよう指示できる. マクロと全く同じだ. しかし,ここでは理想と現実のギャップが出てくる. CLtL2 (p. 229)には「コンパイラがこの宣言を無視するのはフリーである」とあり, 幾つかのCommon Lispコンパイラは確かに無視している. そのようなコンパイラを使わざるを得ないのなら, 関数呼び出しの節約にマクロを使うのも認められるだろう.

効率とLispとの緊密な連携との長所が相俟って, マクロを使おうという考えが強い説得力を持つ場合がある. 第19章のクエリ・コンパイラでは,コンパイル時に移せる計算の量がとても大きいので, プログラム全体を1つの巨大なマクロに変えてしまってよいといえる. この変換はスピードのために行われたのだが, プログラムをLispと緊密にすることにもつながっている. 新しいヴァージョンではクエリの中で数値評価などのLispの式を簡単に使えるのだ.

マクロの短所

  1. 関数はデータだが,マクロはコンパイラへの指示に近い. 関数は(例えばapplyに)引数として渡すことも,関数から返すことも, データ構造内に格納することもできる. マクロではそれらはどれも不可能だ. これらはマクロ呼び出しをλ式で包むことで実現できる場合がある. この方法は例えばマクロにapplyfuncallを適用したいときに使える.
    > (funcall #'(lambda (x y) (avg x y)) 1 3)
    2
    
    しかしこの方法は不便だし,必ず使えるというものではない. avgのように,マクロに&restパラメータがあっても, それに可変個の引数を渡す方法はない.
  2. ソースコードの明確さ. マクロ定義は,同等な関数の定義より読み辛くなりがちだ. だから何かをマクロとして書いてもプログラムが大して進歩する訳でもないなら, 代わりに関数を使った方がよい.
  3. 実行時の明確さ. マクロは関数よりデバッグし辛いことがある. 多数のマクロ呼び出しを含むコードで実行時エラーに出くわしても, backtraceで見えるコードはそれら全てのマクロ呼び出しの展開形からなり, 元々書いたコードとは似ても似つかないかもしれない.
    マクロは展開時に消えてしまうので,実行時には動作の説明がつかない. 普通はtraceでマクロが呼び出される様子を見ることはできない. たとえ使っても,traceはマクロ呼び出しそのものではなく, マクロを展開する関数の呼び出しを表示するだろう.
  4. 再帰. マクロ内で再帰を使うのは,関数のときほど簡単でない. マクロ展開の結果の関数は再帰的でもよいが,展開そのものは再帰的であってはならないからだ. 第10.4章でマクロにおける再帰を扱う.

いつマクロを使うかを決める際には,これらの考慮のバランスを取らなければならない. どちらが有利かを教えてくれるのは経験のみだ. しかしこの後の章のマクロの例は,マクロが便利な状況を大体カヴァーしている. 考え中のマクロがここでの例に似ているなら,そのように書いても大丈夫だろう.

最後に言いたいのは,実行時の明確さ(ポイント6)はほとんど問題にならないということだ. 大量のマクロを含むコードのデバッグは,思った程難しくないだろう. マクロ定義が数百行に及ぶのなら,実行時にその展開形をデバッグするのは嫌になることだ. しかし少なくともユーティリティは,小さく信頼できる層として書かれることが多い. 一般的には定義は15行以下になる. だからもしbacktraceを通してコードとにらめっこする羽目になっても, そのようなマクロが目をひどく曇らせることはないだろう.

マクロの応用例

マクロで何ができるのかを考えた上で,次に浮かぶ質問はこうだ: マクロをどのような応用に使えばよいのだろう?~% マクロの利用についての一般的な説明に一番近いのは, マクロは主に構文変換に使われると言うことだろう. マクロの適用範囲が制限されていると言いたいのではない. Lispプログラムはリストから作られるが \footnote{リストがコンパイラへの入力であるという意味で「作られる」ということだ. 昔の方言の幾つかとは違い,現在では関数はリストから作られてはいない.}, リストはLispの持つデータ構造であり,実際「構文変換」は大変奥が深い. 第19-24章には,構文変換が目的であると言ってよいプログラムの全体を載せた. そしてそれは,結局は全てマクロである.

マクロの応用は, whileのような小さな汎用マクロと, この後の章で定義する大規模で特別な用途を持ったマクロとの間の連続体を形成する. 端点の片方はユーティリティ, つまりどのLisp処理系も組み込みで持っているマクロの類似品だ. それは普通小さく,一般的で,独立して書かれている. しかし特定の種類のプログラムのためのユーティリティを書くこともできる. 例えば,グラフィックス・プログラムの中で使うようなマクロが充実してきたときには, それらはグラフィックスのためのプログラミング言語に似てくるだろう. 連続体のもう片方の端点は, Lispとは明らかに異なった言語でプログラム全体を書けるようにしてくれるマクロだ. この方法で使われるマクロは,埋め込み言語を実装していると言われる.

ユーティリティは,ボトムアップ・スタイルからの帰結の第1番目である. 多層構造を作るにはプログラムが小さ過ぎるような場合ですら, 最下層であるLispの上に層を加えておくことには利点があるかもしれない. ユーティリティnil!は引数にnilを代入するものだが, マクロ以外では定義できない.

(defmacro nil! (x)
   `(setf ,x nil))

nil!を見ると, 「何もしてないじゃないか,打ち込む文字を減らしてるだけだ」と言いたくなる. それは本当だが,マクロのやっていることと言ったら,打ち込む文字の節約が全てだ. 似たような考え方をしたいなら, コンパイラの仕事はマシン語を打ち込む作業を省くことだと言える. ユーティリティの効果は累積していくので,その価値は見過ごせない. 単純なマクロを複数の層に重ねることで, エレガントなプログラムと理解しがたいプログラムとの違いが分かれる.

ほとんどのユーティリティは埋め込まれたパターンだ. 自分のコードの中のパターンに気付いたら,それをユーティリティに変えることを考えよう. パターンとはまさにコンピュータの得意分野だ. 代わりに仕事をしてくれるプログラムが手に入るというのに, どうして頭を悩ます必要があるのだろうか? 何かのプログラムを書いているとき, 色々な場所で同じ構造を持ったdoループを使っていたことに気付いたとしよう.

(do ()
  ((not <条件> ))
  <コード本体> )

コード内で繰り返されるパターンに気付いたとき, そのパターンにはしばしば名前が付けられる. この場合のパターンはwhileという名前だ. それを新しいユーティリティ内で提供したいときには, 条件評価と複数回の評価が必要なので, マクロを使わなければならない. ruriruriページにある下の定義を使ってwhileを定義すると,

(defmacro while (test &body body)
  `(do ()
     ((not ,test))
     ,@body))

上のパターン例は次のように書き換えられる.

(while <条件>
  <コード本体> )

そうすることでコードは短くなり,更に動作内容がはっきりと示される.

引数を変形できる能力のおかげで,マクロはインタフェイスを書くのに便利だ. 適切なマクロを使えば, 長く込み入った式が必要な筈のときでも短く簡潔な式が書ける. GUIのせいでエンド・ユーザのためにそのようなマクロを書く必要は減ってきたが, プログラマがこの類のマクロを使うことは減らない. 一番馴染深い例はdefunだろう. これは関数を束縛するもので,見掛け上はPascalやC等の言語の関数定義に似ている. 第2章では以下の2つの式はほぼ同じ効果を持つことに触れた.

(defun foo (x) (* x 2))

(setf (symbol-function 'foo)
      #'(lambda (x) (* x 2)))

よってdefunは前者を後者へ変換するマクロとして実装できる. defunは次のように書かれていると想像できる \footnote{明確さのために, この例ではdefunが行わなければならない細々とした仕事を全て無視している.}.

(defmacro our-defun (name parms &body body)
  `(progn
     (setf (symbol-function ',name)
           #'(lambda ,parms (block ,name ,@body)))
     ',name))

whilenil!等のマクロは,汎用ユーティリティであると言える. それらはどんなLispプログラムで使ってもよい. しかし特定の領域のためのユーティリティを書いてもよい. 基盤のLispが,拡張のための言語が利用できる唯一のレベルだと考える理由はない. 例えばCADプログラムを書いているのなら,2層に分けて書くことが一番の結果を生むだろう. CADプログラムのための言語(お好みならツールキットという穏やかな言葉でもいいが)と, その層の中で書かれた,ある特定のアプリケーションだ.

(defun move-objs (objs dx dy)
  (multiple-value-bind (x0 y0 x1 y1) (bounds objs)
    (dolist (o objs)
      (incf (obj-x o) dx)
      (incf (obj-y o) dy))
    (multiple-value-bind (xa ya xb yb) (bounds objs)
      (redraw (min x0 xa) (min y0 ya)
              (max x1 xb) (max y1 yb)))))

(defun scale-objs (objs factor)
  (multiple-value-bind (x0 y0 x1 y1) (bounds objs)
    (dolist (o objs)
      (setf (obj-dx o) (* (obj-dx o) factor)
            (obj-dy o) (* (obj-dy o) factor)))
    (multiple-value-bind (xa ya xb yb) (bounds objs)
      (redraw (min x0 xa) (min y0 ya)
              (max x1 xb) (max y1 yb)))))
\caption{元々のmovescale.} \label{fig:OriginalMoveAndScale}

他のプログラミング言語では当り前だと思われているあれこれの区別は, Lispではぼやけている. 他のプログラミング言語では, コンパイル時と実行時,プログラムとデータ,プログラミング言語とプログラムの間には, 概念的な区別が確かに存在する. Lispでは,これらの区別は言葉の上での慣習として存在するに過ぎない. 例えば,プログラミング言語とプログラムを区切る線は存在しないのだ. 問題に適した所に,好きなように手書きの線を引いてよい. 背後のコードの層をツールキットと呼ぶかプログラミング言語と呼ぶかは, 全く用語の問題に過ぎない. プログラミング言語とみなすことの長所の一つは, Lispでやっているのと同じように,それをユーティリティで拡張する気になれることだ.

インタラクティブな2Dドロー・プログラムを書いているとしよう. 話を簡単にするため,そのプログラムが扱うオブジェクトは線分のみとする. 線分は始点xyとベクタdxdyで表現される. その種のプログラムが対応しなければならない操作に, オブジェクトのグループの平行移動がある. それが第\ref{fig:Move-Objs}図の関数move-objsの役目だ. 効率を考え,操作毎にスクリーン全体を再描画することは避けたい ---変更のあった部分だけを再描画したい. そのため2個あるboundsの呼び出しは, オブジェクトのグループを囲む長方形を表す4個の座標 (min x, min y, max x, max y)を返す. move-objsの動作部分は, 移動前と移動後の長方形をそれぞれ見つけるための boundsの呼び出し2個に挟まれており,影響を受けた領域全体を再描画する.

(defmacro with-redraw ((var objs) &body body)
  (let ((gob (gensym))
        (x0 (gensym)) (y0 (gensym))
        (x1 (gensym)) (y1 (gensym)))
    `(let ((,gob ,objs))
       (multiple-value-bind (,x0 ,y0 ,x1 ,y1) (bounds ,gob)
         (dolist (,var ,gob) ,@body)
         (multiple-value-bind (xa ya xb yb) (bounds ,gob)
           (redraw (min ,x0 xa) (min ,y0 ya)
                   (max ,x1 xb) (max ,y1 yb)))))))

(defun move-objs (objs dx dy)
  (with-redraw (o objs)
               (incf (obj-x o) dx)
               (incf (obj-y o) dy)))

(defun scale-objs (objs factor)
  (with-redraw (o objs)
               (setf (obj-dx o) (* (obj-dx o) factor)
                     (obj-dy o) (* (obj-dy o) factor))))
\caption{filletedされたmovescale.} \label{fig:Move-Objs}

関数scale-objsは,オブジェクトのグループの大きさを変えるためのものだ. 拡大率に応じてグループを囲む領域は広がったり狭まったりするので, この関数もboundsの2個の呼び出しの間で動作しなければならない. プログラムを書き進めていけば,このパターンが更に現れるだろう. 回転,裏返し,転置等だ.

マクロによってこれらの関数が共通して持つコードを抽象化することができる. 第\ref{fig:Move-Objs}図のマクロwith-redrawは第\ref{fig:OriginalMoveAndScale}の 関数が共有している骨格を与えている \footnote{このマクロの定義にはgensymが使われているので,次章の知識が必要になる. その目的はまもなく説明する.}. その結果,それらの関数はそれぞれ4行の長さで定義できた. これら2つの関数に利用できたことでその新マクロはすでに簡潔さの点で元が取れたことになる. そしてこれらの関数は,スクリーン再描画の詳細が抽象化されたら,何と明確になったことだろう.

with-redrawは,インタラクティブ・ドロー・プログラムを書くための プログラミング言語の一構造として捉えることができる. そのようなマクロを更に整備していけば, それらは名前の上だけでなく,実際にプログラミング言語に似てくるだろう. そして書いているアプリケーション自身も, その特定の用途のために定義されたプログラミング言語で書けばこうなるだろうか, と思える程エレガントなものに変わっていく.

マクロの主要な利用法のもう一つは,埋め込み言語の実装だ. Lispはプログラミング言語を書くのに際立って優れたプログラミング言語だ. それはLispプログラムはリストとして表現できるが, Lispにはそのように表現されたプログラムに対する組み込みパーサ(read)と コンパイラ(compile)があるからだ. 大抵はcompileを呼ぶ必要もない. 埋め込み言語は,変換を行うコードをコンパイルすることで自動的にコンパイルさせられる (pnkページ).

埋め込み言語とはLispの上に書かれたというよりは混じり合って書かれたものである. そのため,文法はLispとそのプログラミング言語に特有の構造との混合になる. 埋め込み言語を実装する素朴な方法は,Lispでそのインタプリタを書くことだ. もし可能ならばもっと良いアプローチがある. 変換によってそのプログラミング言語を実装することだ. すなわち,評価させたいときにはLispインタプリタが動作するようなLispコードに 個々の式を変換するのだ. そこにマクロの活躍の場がある. マクロの仕事は正にある種の式を別の式に変換することだから, 埋め込み言語を書く際にはマクロは自然な選択だ.

一般的には,埋め込み言語はなるべく変換によって実装できた方がよい. 第1に,労力が少なくて済む. 例えば新しいプログラミング言語に算術演算機能があれば, 数値を表現し,操作することの面倒に一切関わらずに済む. Lispの算術能力が目的に取って十分なものなら, 新しい算術式を等価なLispの算術式に変換し,残りはLispに任せるだけで済む.

普通,変換を使うと,埋め込み言語を速くすることにもなる. インタプリタは本質的に速度に関して不利である. 例えばあるコードがループ内にあるとしよう. コンパイルされたコードでは1回で済む動作を, インタプリタでは繰り返しの度にしなれけばならないことがしばしばある. そのため自前のインタプリタによる埋め込み言語は, そのインタプリタそのものがコンパイルされたときでも遅い. しかし埋め込み言語の式がLispの式に変換されていれば, そのコードはLispコンパイラでコンパイルできる. そのように実装された埋め込み言語では, 実行時のインタプリテーションのオーヴァヘッドに一切悩まされずに済む. 自分のプログラミング言語のための本当のコンパイラを書くのでなければ, マクロによって一番の性能が得られるだろう. 実際,埋め込み言語を変換するマクロは,埋め込み言語のコンパイラと見なせる ---その動作のほとんどを既存のLispコンパイラに依存しているだけのことだ.

第19-25章は全てその話題にあてられているので, ここでは埋め込み言語の例について考えることはしない. 第19章では特に埋め込み言語のインタプリティングと変換の違いを扱い, その2通りで同じプログラミング言語を実装して見せる.

Common Lispについてのある本では,マクロの適用領域が限られていると警告している. その根拠として,CLtL1で定義されているオペレータのうち, マクロは10%にも満たないことを引用している. これは家が煉瓦で作られているから,家具も煉瓦で作ろうと言うようなものだ. Common Lispプログラム内のマクロの比率は,その用途に完全に依存する. マクロを一切使わないプログラムもあれば,全てマクロから成るプログラムもある.


←: マクロ     ↑: On Lisp     →: 変数捕捉

Copyright (c) 2003-2011 野田 開     NODA Kai <nodakai@gmail.com>