共有配列のサイズをむりやり変更する
Common Lispの共有配列の話題です。
共有配列の拡張に関して、細かい所がはっきりしなかったので、
実験した内容をメモとして残します。
共有配列とは、別の配列を参照するだけの配列のことです。
make-array
に:displaced-to
引数を指定することで作成できます。
例をあげます。
(setq aaa (make-array 10 :initial-contents '(a b c d e f g h i j))) -> #(A B C D E F G H I J) (setq bbb (make-array 7 :displaced-to aaa)) -> #(A B C D E F G)
配列aaa
は単なる配列であり、配列bbb
はaaa
を参照しています。
配列bbb
はただ参照しているだけなので、どちらかが変更されると、
もう片方も変更されたように見えます。
要素を変更してみます。
(setf (aref aaa 2) :hello) -> :HELLO aaa -> #(A B :HELLO D E F G H I J) bbb -> #(A B :HELLO D E F G)
共有配列を作成するときは、参照先をはみ出すように作成するとエラーになります。
例えば下記の通り。
(make-array 15 :displaced-to aaa) -> ERROR ★配列aaaは10個しかない
しかし、タイトルにもあるように、本題はこのサイズ関係を無視して、 無理やりサイズを変更することにあります。
参照先のサイズを変更する
サイズの変更は、adjust-array
関数を使います。
この関数は、変更しようとする配列の:adjustable
の値によって動作が変わります。
まずは:adjustable
がnil
の場合を考えましょう。
配列がこんな感じで作成された場合です。
(make-array 10 :adjustable nil)
引数:adjustable
には厄介なことがあります。
この引数は、実は絶対ではなく努力目標みたいなものです。
Common Lispの規格では、:adjustable
をnil
とした場合でも、
t
と同じ挙動することが許されています。
つまり絶対にnil
にするという方法は存在しないのです。
しかし確認すると、少なくともsbcl
, clisp
, ccl
では
t
でもnil
でも指定したものに作成することができました。
確認は次のようにして行います。
(adjustable-array-p (make-array 10 :adjustable nil)) -> NIL ★nilになっている
今回は:adjustable
をちゃんと設定できないと話が進まないので、
:adjustable
がnil
で作成できたとします。
もし:adjustable
がnil
の場合は、
配列aaa
に対してadjust-array
で変更を行おうとしても、
その配列aaa
には変更が生じません。
ただ単純に新しい配列を作成して返却しているだけです。
例をあげます。
(setq aaa (make-array 10 :initial-contents '(a b c d e f g h i j))) -> #(A B C D E F G H I J) (setq bbb (make-array 7 :displaced-to aaa)) -> #(A B C D E F G) (setq ccc (adjust-array aaa 5)) → #(A B C D E) ★問題ないが、変更ではなく新規作成になる (eq ccc aaa) -> nil ★違う配列 (setf (aref ccc 3) :hello) ★adjust-arrayの新規配列を変更 aaa -> #(A B C D E F G H I J) ★変更なし bbb -> #(A B C D E F G) ★変更なし ccc -> #(A B C :HELLO E) ★変更あり、ただcccだけが変更された
では:adjustable
がt
の場合はどうなるのでしょうか?
下記の例を考えます。
(setq aaa (make-array 10 :initial-contents '(a b c d e f g h i j) :adjustable t)) -> #(A B C D E F G H I J) (setq bbb (make-array 7 :displaced-to aaa)) -> #(A B C D E F G) (adjustable-array-p aaa) -> T ★adjustableになっている
サイズの変更を行います。
(setq ccc (adjust-array aaa 5)) -> #(A B C D E) ★5個だけど問題ないの? (eq ccc aaa) -> T ★同じ配列
本来、この時点でエラーなのは分かるでしょうか。
参照先である配列aaa
は、サイズが10個だったからこそ、
配列bbb
を7個で作成できたのです。
しかし配列aaa
は、サイズ5個に減らされてしまいました。
少なくとも、いま配列bbb
をmake-array
で作成しようとするとエラーです。
(setq bbb-error (make-array 7 :displaced-to aaa)) -> ERROR, ★配列7個じゃなくて、5個以下にしてね
なぜadjust-array
を実行した時点でエラーにならないかというと、
その時点では一体誰に:displaced-to
されているか分からないからです。
では、配列bbb
にアクセスするとどうなるでしょうか?
処理系によって行動が違います。
・sbcl (format t "~S~%" bbb) -> #() ・clispとccl (format t "~S~%" bbb) -> ERROR、★配列が小さい
format
関数は全ての配列にアクセスしようとするので上記のような結果になります。
実はclisp
とccl
は、個別にアクセスすることができます。
・clispとccl (aref bbb 2) →C
clisp
とccl
では、参照先のデータにアクセスできるものの、
sbcl
は#()
になっているようなので、それすら許されませんでした。
参照元のサイズを変更する
参照元のサイズ変更とは、配列bbb
の変更を行うことです。
普通に行う分ならmake-array
と変わらず、参照元のサイズを超えたらエラーです。
(setq aaa (make-array 10 :initial-contents '(a b c d e f g h i j))) -> #(A B C D E F G H I J) (setq bbb (make-array 7 :displaced-to aaa)) -> #(A B C D E F G) (setq ddd (adjust-array aaa 15 :displaced-to aaa)) -> ERROR ★15個ではなく10個以下にしてね
vector-push-extend
関数を使う
サイズを拡張する方法は、adjust-array
の他に、
vector-push-extend
関数もあります。
この関数は、:fill-pointer
の配列のみを受け取り、
必要に応じてサイズの拡張を行うというものです。
配列はadjustable
である必要があるのですが、
sbcl
ではそこまで要求していないようです。
参照先であるaaa
にvector-push-extend
を使うのは何も問題ありません。
しかし参照元であるbbb
にvector-push-extend
を使って、
もし拡張が行われると共有配列が解除されてしまいます。
確認してみましょう。
まずは配列を:fill-pointer
で作り直します。
(setq aaa (make-array 10 :initial-contents '(a b c d e f g h i j))) -> #(A B C D E F G H I J) (setq bbb (make-array 7 :displaced-to aaa :fill-pointer t :adjustable t)) -> #(A B C D E F G)
この状態では、参照されている状態です。
(setf (aref aaa 1) :xx) -> :XX aaa -> #(A :XX C D E F G H I J) bbb -> #(A :XX C D E F G)
vector-push-extend
で拡張して、共有を解除してみます。
(vector-push-extend :yy bbb) -> 7 aaa -> #(A :XX C D E F G H I J) bbb -> #(A :XX C D E F G :YY)
共有状態が解除されたということは、
領域が拡張されたタイミングで
参照先のコピーを作成したということになります。
すでに共有状態ではないので、片方の変更はもう片方に反映されません。
(setf (aref aaa 2) :zz) -> :ZZ aaa -> #(A :XX :ZZ D E F G H I J) bbb -> #(A :XX C D E F G :YY)
領域の拡張は、:displaced-to
がないadjust-array
で行っているから
このような結果になっているのでしょう。
内部では次のような命令が実行されているのだと思います。
(adjust-array bbb 8 :fill-pointer t)
補足ですが、vector-push-extend
を実行したときに、
まだfill-pointer
で設定した長さに余裕がある場合は
領域の拡張が行われないので、共有状態は解除されません。