Common Lispは、さまざまな方法で汎変数を拡張することをユーザに許す3個の
マクロ、define-modify-macro、defsetf、そして
define-setf-methodを定義する。
このマクロはincfやdecfに類似する“read-modify-write”
マクロを定義する。マクロnameはarglistによって記述される
追加の引数が続くplace引数をとるために定義される。呼び出し
(name place args...)
は下記に展開されるが
(callf func place args...)
これは順に大まかに以下と同等である。
(setf place (func place args...))
たとえば:
(define-modify-macro incf (&optional (n 1)) +) (define-modify-macro concatf (&rest args) concat)
&keyはarglistの中には許されないが、&restは関数へ
キーワードを渡すためには十分なことに注意せよ。
Common Lispで定義される修正マクロのほとんどは
define-modify-macroのパターンに正確には従っていない。たとえば、
pushはよくない順序で引数をとり、popは完全に変則である。
get-setf-methodを使って“手で”これらのマクロを定義できるし、
内部のsetfビルディングブロックをどのように使うかを見るために
ソースファイルcl-macs.elを調べることもできる。
これは2個のdefsetfフォームのより単純な方である。access-fn
が場所をアクセスする関数の名前である場合、これは対応する格納関数として
update-fnを宣言する。その後、
(setf (access-fn arg1 arg2 arg3) value)
は以下に展開される。
(update-fn arg1 arg2 arg3 value)
update-fnは真の関数か、関数のような方法でその引数を評価する
マクロであることが必須である。また、update-fnはその結果として
valueを戻すことを期待される。さもなければ、上の展開はsetf
が振る舞うことになっていることのための規則に従わないだろう。
特殊な(非Common Lisp)拡張として、defsetfへのtの第3引数は
update-fnの戻り値は適切でないと言っているので、上のsetf
はより以下に似たようなものへ展開されるべきである。
(let ((temp value)) (update-fn arg1 arg2 arg3 temp) temp)
setfメソッドの標準一式から引かれた、defsetfの使用のいくつかの
例は:
(defsetf car setcar) (defsetf symbol-value set) (defsetf buffer-name rename-buffer t)
これは2番目の、より複雑なdefsetfのフォームである。それは追加の
store-var引数を除いてむしろdefmacroと似ている。
formsは、arglistによって記述された引数をともにする
access-fnへの呼び出しによって形成された汎変数にstore-var
の値を格納するLispフォームを戻すべきである。formsはsetf
メソッドを文書化する文字列で始まってもよい(関数の先頭に現れる
文書文字列と類似している)。
たとえば、defsetfの単純なフォームは以下の簡略表記法である。
(defsetf access-fn (&rest args) (store) (append '(update-fn) args (list store)))
戻されるLispフォームは制限されていない仕方でarglistや
store-varからの引数へアクセスできる; このsetf-methodを起動する
setfやincfのようなマクロは、評価の明白な順序が
保存されることを確実にするために必要な一時的変数を挿入する。
標準パッケージから引かれた別の例:
(defsetf nth (n x) (store) (list 'setcar (list 'nthcdr n x) store))
これは新しいplaceフォームを作るための最も一般的な方法である。
arglistに記述された引数と一緒にaccess-fnへのsetfが
展開されると、formsは評価されて5項目のリストを
戻さなければならない:
gensymへの
呼び出しから得られる)。
これは同じ名前のCommon Lispマクロとほとんど同じだが、メソッドは5個の 値自身ではなく5個の値のリストを戻す点は異なる。それはEmacs Lispは 多重戻り値のCommon Lispの記法をサポートしていないからである。
もう一度、formsは文書文字列で始まってもよい。
setfメソッドは一時変数に関して最大限に保守的であるべきである。
defsetfによって生成されるsetfメソッドの中で、第2戻り値は単に
placeフォーム中の引数リストであり、第1戻り値はgensymによって
生成される対応する一時変数の数のリストである。setfやincf
のようにこのsetfメソッドを使うマクロは不要とわかった多くの一時変数を
最適化するので、setfメソッド自身を最適化する理由はほとんどない。
この関数は、defsetfやdefine-setf-methodによって以前に
記録された定義を呼び出すことで、placeのためのsetfメソッドを
戻す。結果は上述した5個の値のリストである。あなた自身のincfに
似た修正マクロを作るためにこの関数を使える(実際は内部関数
cl-setf-do-modifyやcl-setf-do-storeを使う方がよい。
少しだけ使いやすく、かなりの最適化も行なう; 単純な例としてincf
関数のソースコードを調べよ)。
引数envは、get-setf-methodがplaceのマクロを展開する
必要がある場合にmacroexpandへ渡される“環境”を指定する。それは
マクロへの&environmentか、get-setf-methodを呼んだsetf
メソッドから来るべきである。
applyやsubstringのためのsetfメソッドのソースコードも
参照のこと。それぞれはより単純な場合にget-setf-methodを
呼び出すことによって動作し、それからさまざまな方法で結果を
マッサージする。
現代のCommon Lispは関数のsetfの振る舞いを指定するための第2の、
独立した方法を定義する。すなわち、その名前がシンボルではなくリスト
(setf name)である“setf関数”である。たとえば、
(defun (setf foo) …)は、setfがfooに適用する
際に使われる関数を定義する。このパッケージは、現在はsetf関数を
サポートしない。まだdefsetfされていないか宣言されていない
フォームでsetfを使うことはコンパイル時エラーである; より新しい
Common Lispでは、関数(setf func)は後で
定義されるかもしれないのでこれはエラーではないだろう。