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)
は後で
定義されるかもしれないのでこれはエラーではないだろう。