symbol-macroの妙

今日は入門向けの内容ではなく、
Common Lispにある数あるマクロの一つ「シンボルマクロ」についてです。

symbol-macro

通常のマクロはfunctionのように呼び出しフォームの先頭に出現したときに動作します。
シンボルマクロはその逆に、シンボルとして出現したときに動作するマクロです。

シンボルマクロは式に名前を付けることができます。
通常では使う必要はない気もしますが、
使いどころによってはコードの可読性を上げることが可能です。

マクロは諸刃の剣なので使い方を誤れば危険ですが、
うまく利用すればコーディングが著しく効率的かつ簡潔になりますので、
使い方を考えてみましょう。

長い式に名前を付けてみる

ちょっと長くて冗長な関数を作ってみました。

(defvar *cursor* 0)
(defvar *target* '(10 20 30 40 50))

(defun watch (elt *target* *cursor*))

(defun clamp (v l r) (min (max v l) r))

(defun cursor-move (&optional (count 1))
  (setf *cursor* (clamp (+ *cursor* count) 0 (length *target*)))))

cursor-moveは複雑な式になり可読性が落ちています。
原因はclampの引数となっている式が長いためです。

もちろんletを使って名前付けしてもよいですが、
下手に局所変数を利用するとコンパイル時最適化の性能が悪い場合は、
実行速度に影響するかもしれません。

(defun cursor-move (&optional (count 1))
  (symbol-macrolet ((next (+ *cursor* count))
                    (length *target*))
    (setf *cursor* (clamp next 0 length)))))

letの代わりにsymbol-macroletを利用するとこんな感じ。
全体としては長い式になっていますが構造化されたおかげで、
(setf ...)となっているメインの式は短く収まっています。

参照ごとに値が不定な変数に見せかける

(define-symbol-macro *rand* (random 1.0))

変数*rand*はアクセスするたびに異なる値に評価されるように
見せかけてコーディングすることができます。

繰り返し出現する副作用のある式を省略する

副作用がない式は一度評価を記憶して利用するとよいのですが、
副作用がある式が何度も登場する場合は、symbol-macroで省略するのもありかも。

見切り発車

シンボルマクロについて書き始めようとしたけれど、思いのほかネタが出てこなかったので終わり。
On Lispにシンボルマクロの使用例があるので、そちらを参考にすると良いかもしれないです。