subseq は新しいリストを作る・・・だと?

(eq (nthcdr 1 #1='(0 1 2)) (subseq #1# 1))
|
nil

(eq (nthcdr 1 #1='(0 1 2)) (nthcdr 1 #1#))
|
t

(eq (subseq #1='(0 1 2) 1) (subseq #1# 1))
|
nil

 上の1番目から明らかなように、subseqで切り出されたものはnthcdrで分けられたものとは異なる。2番目は、nthcdrで得られるリストが同一であるか確かめている。eqで等しいということは、全く等価なオブジェクトであることを示すから、新しいリストを作っているわけではないことが分かる。3番目は、subseqで得られるリストが同一でないことを示している。つまり、subseqは新しいリストを作っているのだ。では、なぜsetfで代入ができるのだろう。

nthcdrとsubseq - 象徴ヶ淵

えー。全然気づいてなかった。

話逸れるけど、「同一」が eq で「等価」は equal な気がする。ニホンゴ、ムツカシイ。

で。とゆーことは、だ。

(setq list '(0 1 2 3 4 5 6 7 8 9))
=> (0 1 2 3 4 5 6 7 8 9)

(subseq list 4)
=> (4 5 6 7 8 9)

;;; subseq で切り出した(つもりの)部分リストを変更しても
(let ((part (subseq list 4)))
  (setf (cdr part) '(foo bar baz)))
=> (foo bar baz)

;;; 元のリストは変わってない
list
=> (0 1 2 3 4 5 6 7 8 9)


(nthcdr 4 list)
=> (4 5 6 7 8 9)

;;; nthcdr で切り出した部分リストを変更すると
(let ((part (nthcdr 4 list)))
  (setf (cdr part) '(foo bar baz)))
=> (foo bar baz)

;;; 元のリストも変わってる
list
=> (0 1 2 3 4 foo bar baz)

なにこの罠。

余談: マクロ書くとき

マクロ書くときは、受け取った式をごにょごにょするのによくリスト操作するんだけど、自分の場合は

(cons (car form) (do-something-magical (cdr form)))

みたいに、非破壊的な操作ばっかしてる。car と cdr で考えてると subseq は(たぶん考え方が違うから)わかりにくいんで、あんま使わない。マクロ展開のコストはコンパイルしてしまえば消えて無くなるんで、パフォーマンス気になるならまずはコンパイルしろってことで、全然気にしてない。

おまけ: nthcdr への setf

俺の理解では nthcdr というのはこーゆー関数。

(defun my-nthcdr (n list)
  (if (zerop n) list
    (my-nthcdr (1- n) (cdr list))))

なので、破壊的代入はこんなん。subseq と違って長さ変わっちゃうけど。
#追記 2009-07-20: これだと n=0 のとき困ったことになるです。というかこれでなくても困ります

(defsetf nthcdr (n list) (new)
  `(setf (cdr (nthcdr (1- ,n) ,list)) ,new))
=> (setf nthcdr)

(setq list '(a b c d e f g h i))
=> (a b c d e f g h i)

(setf (nthcdr 3 list) '(1 2 3 4 5))
=> (1 2 3 4 5)

list
=> (a b c 1 2 3 4 5)

こだわるなら展開時に n がわかるなら、cdr とか cddr とか cdddr とか cddd(ryに展開した方が、実行時のパフォーマンスはいいかも。*1

*1:xyzzy だと built-in 関数使った方がたぶん速い