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では用いることができないが、元のリストの部分リストとして扱うことができる。それぞれの特性を考えて組む必要があるだろう。