遅れてきた人によるメモ

遅れてきた人は危険がいっぱい

Common Lispの(defun (setf fun) (val a) ...)について

承前

Common Lispでイマイチよく分からなかったことを書こうと思う。on Lispでもちょこっと出てくる、(defun (setf fun) (val a) ...)について。まず調べようとしても、検索しにくい、とても検索にしにくい。説明すると、(setf (fun a) val)を定義するためのdefunです。valとaがひっくり返っているところに注意(正確にはaは一番最後になる)。これが活躍するケースを元に説明したいと思う。

構造体は、defstructを行った時点で、accessorなどがdefunで作成される。

CL-USER> (defstruct hoge foo-list bar-list)
HOGE

 この例でいうと、readとしてのhoge-foo-list, hoge-bar-listとwriteとしての(setf hoge-foo-list), (setf hoge-bar-list)、さらに新しいinstanceを作成するmake-hoge、コピーのためのcopy-hoge、構造体hogeの判定hoge-pである。お目当の(setf hoge-foo-list)が出てきた。詳細は、(macroexpand-1 '(defstruct hoge foo bar))で確認してね。

hogeの中のfoo-listとbar-listに数字をpushすることを考える。そして、pushされた数字は、昇順にlistへ格納されるようにすることとする。1つずつ定義するのは簡単で、

(defmethod push-foo ( (v number) (h hoge))
  (setf (hoge-foo-list h) (sort (push v (hoge-foo-list h)) #'<)))

みたいになる。ただ、明らかにbarも同じ形式なので関数は共通化したい。すると、こうなる。

CL-USER> (defmethod %push-child ( (fun symbol) (v number) (h hoge))
           (let ( (lst (funcall fun h)))
             (funcall `(setf ,fun) (sort (push v lst) #'<) h)))
#<STANDARD-METHOD %PUSH-CHILD (SYMBOL NUMBER HOGE)>

1つ前のCommon Lispでfoldrを書いてみる - 遅れてきた人によるメモの復習になるけど、変数に関数を入れる場合は、funcallが必要。ただ、setfの場合は、単純に(setf (funcall fun h) lst)はダメで、本来の定義である(setf fun)という形に戻す必要がある。ちなみに、(fdefinition `(setf ,fun))としても大丈夫。上の対応関係で色を塗ると、下のように見やすい定義となる。

CL-USER> (defmethod push-foo ( (v number) (h hoge))
           (%push-child 'hoge-foo-list v h))
#<STANDARD-METHOD PUSH-FOO (NUMBER HOGE)>


CL-USER> (defmethod push-bar ( (v number) (h hoge))
           (%push-child 'hoge-bar-list v h))
#<STANDARD-METHOD PUSH-BAR (NUMBER HOGE)>

 実際にpushした例

CL-USER> (setf hoge-instance (make-hoge))

#S(HOGE :FOO-LIST NIL :BAR-LIST NIL)

; push foo
CL-USER> (push-foo 1 hoge-instance)
(1)
CL-USER> (push-foo 2 hoge-instance)
(1 2)
CL-USER> (push-foo 3 hoge-instance)
(1 2 3)

 

; push bar
CL-USER> (push-bar 4 hoge-instance)
(4)
CL-USER> (push-bar 2 hoge-instance)
(2 4)
CL-USER> hoge-instance
#S(HOGE :FOO-LIST (1 2 3) :BAR-LIST (2 4))

fooの方はsortしてなかったら、(3 2 1)となるパターンで、barの方は(2 4)となるパターン。

■結論

関数が変数funのときにsetfを適用する必要があったら、(setf fun)という形式に一度戻して考えましょう。

■余談

sortもstable-sortも破壊的な関数なので、上書きしない場合は、(copy-seq lst)とかやってコピーしないと以前の値がズタズタにされてしまうので注意。