(setf getf)の定義が難しかった
getf
はplist
の値を操作するアクセス関数です。
取得する方の構文は、
(getf plist indicator &optional default)
みたいな感じです。
設定の方はこんな感じ。
(setf (getf place indicator &optional default) new-value)
説明では、setf
でgetf
を使うときにdefault
が無視されると記載されています。
ああそうなんだ、default
は無視でいいんだなと思って、
ただ式を評価するだけで値を捨てていたのですが、
規約をちゃんと見直すと、setf
マクロのときだけdefault
の意味がないのであって、
例えばincf
マクロではちゃんと使うよ、と例文付きで紹介されていました。
それがこれ。
(let ((plist '())) (incf (getf plist 'count 0)) plist) => (COUNT 1)
これ、どうやって実現するんだ?
まず(setf getf)
の定義はdefun
では無理で、
必ずdefine-setf-expander
を使う必要があります。
なぜならオブジェクトに設定するのではなく、
nil
の場合はplace
にcons
を設定する必要があるためです。
最初はこんな感じで作成していました。
(define-setf-expander getf-error (place indicator &optional default) (with-gensyms (g1 g2 g3 g4) (multiple-value-bind (a b g w r) (get-setf-expansion place) (let ((g3 (car g))) (values `(,g2 ,g4 ,@a) `(,indicator ,default ,@b) `(,g1) `(let ((,g3 (setplist ,g2 ,g1 ,r))) ,w ,g1) `(getf ,r ,g2))))))
こいつはいつ見てもわかりづらい。
式自体は簡単なのですが、引数にplace
があるので、
いったい何の値をどうしたいのか混乱してしまいます。
上記の文は、write
項にsetplist
という関数が使われていますが、
これはplist
の設定関数です。
こんな感じ。
(defun setplist (key value list) ...)
もしlist
にkey
があればvalue
を設定し直し、
list
にkey
が無ければ(key value)
をlist
に追加して返却するというもの。
Lispで書けばごちゃごちゃしてわかりづらいですが次のようになります。
(defun setplist (key value list) (do ((next list)) ((null next) (list* key value list)) (destructuring-bind (x y . tail) next (when (eql x key) (setf (cadr next) value) (return list)) (setq next tail))))
ではdefault
を追加する場合はどのように修正したらよいでしょうか。
最初はsetplist
関数にdefault
引数を追加しようと思ったのですが、これは間違い。
setplist
の値はvalue
に設定するともう決まっているのですから。
変更はreader
の項に行います。
つまり、
`(getf ,r ,g2)
ではなく、もしlist
にkey
が無かったら、default
を返却するように変更します。
変更内容は下記の通り。
`(multiple-value-bind (,g5 ,g6 ,g7) (get-properties ,r (list ,g2)) (declare (ignore ,g5)) (if ,g7 ,g6 ,g4))
【追記します】
よく考えたら、こんなことしなくても次の書き換えでいいんですね。
`(getf ,r ,g2 ,g4)
何で思いつかなかったのか。
では完成したgetf
のdefine-setf-expander
を下記に示します。
(define-setf-expander getf! (place indicator &optional default) (with-gensyms (g1 g2 g3 g4) (multiple-value-bind (a b g w r) (get-setf-expansion place) (let ((g3 (car g))) (values `(,g2 ,g4 ,@a) `(,indicator ,default ,@b) `(,g1) `(let ((,g3 (setplist ,g2 ,g1 ,r))) ,w ,g1) `(getf ,r ,g2 ,g4))))))
実行してみましょう。
(let (x y) (setf (getf! x 10 20) 30) (format t "~A~%" x) (incf (getf! y 10 20)) (format t "~A~%" y))
結果は下記の通り。
(10 30) (10 21)
うまく行きました。
普通だったら、default
を無視するなんて発想は出てこないと思います。
だって引数に意味がないなら規約で削除しているはずじゃない?
そう思わなかった理由が(setf macro-function)
です。
macro-function
関数は&optional
でenvironment
を受け取ります。
同じように(setf macro-function)
もenvironment
があるのですが、
setf
の方に限りenvironment
が指定されている場合はエラーか無視されます。
sbcl
はエラーで、clisp
, ccl
は無視されました。
無視の意味としては、macrolet
のマクロ関数を変更することができないということです。
なぜ意味のない引数が規約として生きているのかは知りませんが、
この例がずっと頭の中にあったため、
「(setf ...
)の&optional
は意味がない場合がある」と何となく勘違いしてしまいました。