初めての人のためのLISP[増補改訂版]のメモです。
ついにマクロ。
最初:初めての人のためのLISP (第1講-第5講) - もうカツ丼でいいよな
置き換えてから評価する
(defun first (x) (car x))
という関数があったとき
(first (list 1 2 3))
本来なら関数firstはxにリスト(1 2 3)を束縛した後に(car x)を評価するが、そうではなく上記関数を
(car (list 1 2 3))
と置き換えてから評価したいとする。この目的のため、古いLispにはdefsubstと呼ばれるものがあった。
(defsubst first (x) (car x))
のように使う。
defsubstは呼び出し時には引数を評価せず、置き換え後に評価する。呼び出し時に評価するか否かの違いは評価により値の変化するS式を引数とする場合(ifなど)に重要となる。
S式からS式への変換 subst
(subst x y z)
- zの中のyをxに置き換えた新しいリストを返す(substitute x for all y in z)
- サブスト
定義1(全ての要素をコピーする)
(defun subst (new old tree) (cond ((eq old tree) new) ; 葉(アトム)がoldと等しければnewに置き換える ((atom tree) tree) ; そうでなければ葉をそのまま返す (t (cons (subst new old (car tree)) ; 葉でなければcarと (subst new old (cdr tree)) )))) ; cdr方向に再帰
リストを木構造としてとらえている。リストのcarが根。
近頃のLispでは書き換えのおこらなかった部分木についてはコピーしない。
定義2(書き換えの起こらなかった部分木はコピーしない)
(defun subst (new old tree) (cond ((eq old tree) new) ((atom tree) tree) (t (let ((a (subst new old (car tree))) ; carのsubst結果をaに (d (subst new old (cdr tree))) ) ;cdrのsubst結果をdに (cond ((and (eq a (car tree)) ; substの前後でcarも (eq d (cdr tree)) ) ;cdrも変化がなければ tree) ; treeをそのまま帰す (t (cons a d)) )))))) ; 変化していたら新しいリストを作成
letはprogの4つの機能(一時的な変数、go、return、逐次評価)のうち、変数の用意と逐次評価のみを取り出したもの。
(let ((変数1 初期値1) (変数2 初期値2) ... (変数n 初期値n)) 式1 式2 ... 式m)
変数1..nに初期値1..nがセットされ、式1..mが順に評価されたのちに式mの値が帰る。
一括して置き換えるsublis
(defun sublis (alist tree) (let ((pair (assoc tree alist))) ; 葉(アトム)の要素がalistから見つかればpairにドット対がセットされる (cond (pair (cdr pair)) ; 葉が上でヒットしていたらドット対のcdrを返す ((atom tree) tree) (t (let ((a (sublis alist (car tree))) (d (sublis alist (cdr tree))) ) (cond ((and (eq a (car tree)) (eq d (cdr tree)) ) tree ) (t (cons a b)) ))))))
- サブリス
- 引数にalistを与え、一括して置き換え
- assocでヒットするか否かにより、与えられたtreeが置き換え対象のアトムか否かを判断している。
if
if p then x else y
(defun if (p x y) (cond (p x) (t y)))
この場合、呼び出し時にxもyも評価されてしまう。setqを含むS式を渡す場合、想像とは違う動作になる可能性が高い。
もしdefsubstが使えるなら次のように定義することで目的を果たせる。
(defsubst if (p x y) (cond (p x) (t y)))
これは
(if flag a b)
としてifを呼びだすと、
(sublis '((p . flag) (x . a) (y . b)) '(cond (p x) (t y)))
という変換をした後に変換後のS式を評価する。
&rest
condのような特殊形式やlistのような関数は不定個の引数を受け取る。不定個の引数を受け取るには&restを使う。
(defun fun (x y &rest z))
このとき、xとyに続く3番目以降の引数は全てリストにまとめられてzに入る。
マクロ
マクロはdefmacroにより定義する。マクロではまず本体を評価して新しい式を生成し(マクロ展開)、得られた値を呼び出した場所で改めて評価する。また、呼び出し時には引数を評価しない。
(defmacro first (x) (list 'car x))
を
(first (list a b c))
として呼びだすとまずマクロの本体部分が評価され、
(car (list a b c))
が得られる。そしてこれが呼び出した場所で評価される。
バッククォート
バッククォートは基本的にはクォートと変わらない。
'x ;=> x `x ;=> x '(a b c) ;=> (a b c) `(a b c) ;=> (a b c)
ただし、リストに対して用いられた場合、カンマ(,)を用いることでクオートの効果を打ち消すことができる。
(setq b 3) '(a b c) ;=> (a b c) `(a ,b c) ;=> (a 3 c) (list 'a b 'c) ;=> (a 3 c) クォート付きで呼び出したlistからクオートを外すのと同じ
バッククォート記法を使うと、マクロを見やすく記述できる場合がある。
(defmacro first (x) (list 'car x)) (defmacro first (x) `(car ,x))
リストのつなぎ込み
バッククォート中で,@に続くものがリストであるとき、リストは周囲の要素と同じレベルにつなぎ込まれる。
(setq x '(2 3)) `(1 ,@x 4) ;=> (1 2 3 4)
popとpushの定義
(defmacro pop (x) '(prog1 (car ,x) (setq ,x (cdr ,x)))) (defmacro push (y x) '(setq ,x (cons ,y ,x)))
(setq x '(1 2 3)) (pop x) ;=> 1 x ;=> (2 3) (push 5 x) ;=> (5 2 3) x ;=> (5 2 3)