On Lisp 続き 6章~9章

On Lisp を読みつつのメモだったり感想だったりの続き。いよいよマクロへ。

6. 表現としての関数

  • Lisp でネットワークを定義する。
  • DSL だなぁ、すごい。

7. マクロ

いよいよマクロ。

  • 逆クォート
    • ,で打ち消し、,@で展開
    • マクロ展開時に引数に置き換えてほしいものの前に , や ,@ をつけるイメージ
  • マクロ展開の確認
  • 構造化代入
    • よく分からないのでパス
  • マクロのモデル
    • defmacro の簡易実装が書いてある
    • が、理解できない…。ひとまずパス。
  • 関数からマクロへ
    • 単純に変換できない場合の対処法

+ のマクロ版が

(defmacro sum (&rest args)
  `(apply #'+ (list ,@args)))

と定義されていたが、これでもいけた。

(defmacro mysum (&rest args)
  `(funcall #'+ ,@rest))

いつマクロを使うべきか

  • 基本はマクロ
  • マクロが必要なときはマクロ
    • いつ必要かを説明していくよ

マクロにしかできないこと

引数の評価を抑制する。

On Lisp に出てきた while を使ってみる。

(let ((x 10) (sum 0)) 
  (while (> x 0) 
    (incf sum x)
    (decf x)) sum)

すごく手続きっぽい書き方になってしまったが、while の中の setq は x の値によって呼ばれる回数が変わってくる。全く評価されないかもしれない。

  1. 変形(setfパターン)
    • setf は引数として与えられた値に書き込む
    • (let *1) (setf (car x) 9) x)  ; (9 4)
    • 第一引数を見て判断するのでマクロでないとできない
  2. 束縛
    • let や setq などのレキシカルな束縛を変更する処理はマクロが必須
  3. 条件分岐
    • 条件が満たされたときのみ評価するにはマクロが必須
    • (if t (print 3) (print 4)) で 4 が表示されては困る。if が関数なら呼び出す時点で評価されてしまう。
  4. 複数回の評価
    • while など、ループのたびに評価されるようにするにはマクロが必須。
  5. 呼び出し側環境の利用
    • 呼ばれた場所のコンテキストに応じた変数を使える
    • これは危険。通常は使うな。 (On Lisp の本では継続とATNコンパイラで利用している)
  6. 新しい環境を包み込む
    • ちょっと分からん。保留。
  7. 関数呼び出しの節約
    • インラインでも同じ

悩むような微妙なケース。

avg のサンプル

>(defmacro avg (&rest args)
  `(/ (+ ,@args) ,(length args)))
> (pprint (macroexpand-1 '(avg 10 11 12)))
; (/ (+ 10 11 12) 3)

コンパイル時には長さが固定された状態になっている。

長所まとめ:

  1. コンパイル時計算
    • 一部をコンパイル時に展開できる。上の avg みたいな。
    • 詳細は13章
  2. Lisp との連携
    • 一部を Lisp に任せられる。DSL などで有利。
    • よく分からんが詳細は19章とのこと
  3. 関数呼び出しの節約
    • inline でも同じだけど。

短所まとめ:

  1. マクロを渡せない
    • apply に関数を渡すことはできるけど、マクロを渡すことはできない。
    • lambda で擬似的に再現することはできるが不便。
  2. 読みにくい
  3. デバッグしにくい
  4. 再帰が難しい (→10.4 章)

長所と短所のバランスが大事。

で、どう使うの?

  • 構文変換として使うよ。
    • よくあるパターンをマクロで定義すれば、処理が明確になって嬉しいよね。
    • defun も lambda で生成した関数を symbol-function で登録しているに過ぎない
  • DSLとして使うよ。
    • 「再描画しつつ○○する」の共通武運を with-redraw マクロで実装
    • (雑感) lambda でもできなくはなさそうだが、ソースはすっきりするのかな

9. 変数捕捉

マクロ引数の捕捉

  • マクロに渡された変数名が内部で宣言してる変数とかぶってしまう。
  • マクロ内で定義したスコープに本来の値が奪われてしまう。
  • で、どうしたらいいの?はあとで

フリーシンボルの捕捉

  • グローバルなつもりで使っている変数名が、マクロを展開した場所で既に使われている。
  • で、どうしたらいいの?はあとで
  • フリー
    • 束縛されていないこと
    • スコープ内に定義がない
  • 骨格
    • マクロ呼び出し時の引数を取り除いたもの
    • バッククォートを使っている場合は ,x のようなカンマがつくものを取り除いたのが骨格

捕捉可能の定義。

  1. あるシンボルが骨格内にフリーなまま登場する
    • gripe の例
  2. マクロの引数が束縛または評価される骨格の一部に、あるシンボルが束縛されている
    • for の例

for の例を解決するいろんな方法を取り上げる

  • 別の let に入れる
  • gensym する
  • lambda に入れる

*1: x '(3 4