nthcdrとsubseq

nthcdrはn番目のcdrであり、subseqはfromからtoまでのシーケンスを取る。このsubseqをfromだけ指定してリストに適用すると、nthcdrと同じような結果が得られる。

(setq x (list 1 2 3 4 5 6 7 8 9 10))

(nthcdr 3 x)
|
(4 5 6 7 8 9 10)

(subseq x 3)
|
(4 5 6 7 8 9 10)

ところが、これらのコードは同じように扱うことができない。

(defmacro env (&body body)
  `(let ((x (list 1 2 3 4 5 6 7 8))) ,@body x))

(env (setf (subseq x 3) '(a b c d e)))
|
(1 2 3 a b c d e)

(env
 (rplaca (nthcdr 3 x) 'a)
 (rplacd (nthcdr 3 x) '(b c d e)))
|
(1 2 3 a b c d e)

上のコードは、破壊的な代入が行われているかどうかを確かめている。subseqはsetfが使えるが、nthcdrはsetfが使えない。nthcdrでは、どのような代入をすべきか不明なので、setfが使えないのはうなずける。上のような代入が考えられるが、そうしたければ直に書けばいいだろう。では、subseqに対するsetfはどのようなものだろうか。

(env (setf (subseq x 3 6) '(a b c d e)))
|
(1 2 3 a b c 7 8)

(env (setf (subseq x 3) "foobar"))
|
(1 2 3 #\f #\o #\o #\b #\a)

上のような例を見ると、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で代入ができるのだろう。

(get-setf-method '(subseq x 0))
|
(#:G1 #:G2)
(x 0)
(#:G3)
(progn (replace #:G1 #1=#:G3 :start1 #:G2 :end1 nil) #1#)
(subseq #:G1 #:G2)

(macroexpand '(setf (subseq x 0) sequence))
|
(progn (replace x sequence :start1 0 :end1 nil) sequence)

上はsubseqに対するsetfによる代入方法を調べたものである。1番目はなにやら色々出ているが、これらはsetfを展開するための部品である。実際に展開されたものは2番目で示したものだ。これによると、結局、subseqをsetf内で用いるとreplaceによって置換が行われていることが分かる。直観的に理解しやすい動作あるが、実はsubseqは全く関係なかったのだ。


このようになっているので、マクロを書く場合、subseqで置き換えると、setfで用いることができ、setf以外の場所でも同じような感覚で用いることができる。一方、nthcdrで置き換えると、setfでは用いることができないが、元のリストの部分リストとして扱うことができる。それぞれの特性を考えて組む必要があるだろう。