プログラミングを頑張る日記

プログラミングを勉強して、ハッカーになろう

Common Lispを頑張る(41)

イボ治療で液体窒素で指を焼かれ、とても指が痛いです。
Webアプリのことなんてまったく考えられません。
こういうときは...。

「実践Common Lisp」をやります。括弧を書いて心を鎮めるのです。
8章。マクロを自分で定義します。

マクロと関数の違いを理解する

マクロと関数は似ているというのがあります。
Lispで記述され、引数を取り、値を返し、詳細を抽象化してくれる。
でもまったくマクロと関数は異なるものなんだ! という話が最初にあります。

マクロを理解するためには、コードを生成するコードと、最終的にプログラムを構築するコードを
明確に区別しなくてはならないとのこと。前者がマクロで、後者が残りの全てです。
マクロはコンパイラコンパイルするコードを生成するために使うプログラムで、
全てのマクロが展開されたところで、結果として得られるコードがコンパイルされて実行されると。
マクロが動作するのはマクロ展開時で、コードが実行される実行時とは明確に区別されます。

マクロ展開時に動作するコードは実行時とは全く異なる環境で動作するので、その違いを意識せねばなりません。
まず、マクロ展開時は、実行時に存在するデータにはアクセスできません。
ソースコードにあらかじめ存在しているデータのみが扱えます。
例を。

(defun foo (x)
  (when (> x 10) (print 'big)))

普通、xはfooの引数を保持する変数だな、とわかりますが、
マクロ展開時には引数もなにもあったものではありません。
WHENを処理するコンパイラに渡されるのは、(> x 10)とか(print 'big)のような、
ソースコードを表わすLispのリストだそうです。
WHENが下記のように定義されていたら、

(defmacro when (condition &rest body)
  `(if ,condition (progn ,@body)))

バッククォート式は次のようなコードを生成します。

(if (> x 10) (progn (print 'big)))

マクロ展開時と実行時の境界は、コンパイラでなくインタプリタ上では
時間的に絡み合っていてはっきりしないそう。
しかも言語仕様上、インタプリタでマクロがどう扱われるのか規定していないそうです。

ちょっとここまでの話をよく理解できていない気がします。もやもやと書いている。
実際に書いてみたらわかるかも。

DEFMACRO

マクロを定義するために使うのはDEFMACROです。そのまんまですね。
基本的な骨組みは下記の通り。

(defmacro name (parameter*)
  "省略可能なドキュメンテーション文字列"
  body-form*)

関数にとても似ていますが、関数と違い、やりたいことを後で実行するコードを生成するのが仕事になります。

マクロ展開ではLispのパワーが全て使えるそうです。この章ではマクロの表面しかなぞれないそう。
それでもマクロを書く上での一般的な手順はわかるということ。
マクロを書くときは、書けるようにしたいコードがあってマクロフォームのサンプルから
書き始めることもあるし、コードを書いているときに同じようなパターンを繰り返していることに
気付いて、そのパターンを抽象化して分かりやすくするために書くということもあるそうです。

そのどちらで書くのだとしても、マクロを書き出す前には"反対側"について理解する必要があるそう。
マクロを書く最初のステップは、マクロを呼び出すときの例を1つでも書いてみること、
その例がマクロによって展開されるべきコードを書いてみることだそうです。

それができたら次のステップ、実際にマクロを書く。
シンプルなマクロなら、上のようにパラメータを適切に挿入してバッククォートするだけですが、
複雑なものなら補助関数とデータ構造を完備したそれなりの独立したプログラムになるだろうとのこと。

最後は、マクロによる抽象化に漏れがないことを確認するステップ。
漏れがあると、引数によっては動かなかったり、呼び出された環境のコードとの間で
望ましくない作用を引き起こすかもしれないのです。
嬉しいことに、マクロの抽象化が漏れるケースはそんなに多くなく、
チェックする方法さえ知っていれば簡単に回避できるそうです。

さあ、マクロを書くときのステップについて知ったし、書くぞ! というところなのですが、
痛み止めの作用でふらふらしてきました。
続きはまた今度にします。おやすみなさい。