配列モジュール

SMLの配列モジュールを紹介します.
ML系はみんなそうだと思いますが, リストの方が柔軟な操作が可能ですので
パフォーマンス上の理由以外に配列を使うことは無さそうです.
(あとは長さを明示すると分かりやすい時に使うかも知れない?)

Vector vs Array

SMLでは配列モジュールとして Vector と Array が
Basisライブラリ(標準ライブラリ)の一部として提供されています.

  • Vector: 多相的で定数時間アクセスを提供するimmutable配列
  • Array : 多相的で定数時間アクセスを提供する mutable配列

基本的にはupdate関数の振る舞いが異なるだけでほぼ同じ振る舞いをします.

構築

普通のリストから構築またはtabulate関数により構築します.
VectorとArrayは全く同様に構築できます.

Vector
- val v0 = V.fromList [1,2,3,4,5]; (* リストから構築 *)
val v0 = #[1,2,3,4,5] : int vector

- val v1 = V.tabulate (10, fn x=>x*2); (* ジェネレータ関数を渡して構築 *)
val v1 = #[0,2,4,6,8,10,12,14,16,18] : int vector
Array
- val a0 = A.fromList [1,2,3,4,5]; (* リストから構築 *)
val it = [|1,2,3,4,5|] : int array

- val a1 = A.tabulate (10, fn x=>x*2); (* ジェネレータ関数を渡して構築 *)
val a1 = [|0,2,4,6,8,10,12,14,16,18|] : int array

要素へのアクセス

これもVectorとArrayで全く同様に扱うことが出来ます.

- V.sub (v0, 0);    (* 0番目の要素へアクセス *)
val it = 1 : int
- A.sub (a0, 0);    (* 0番目の要素へアクセス *)
val it = 1 : int

- V.sub (v0, 5);  (* 範囲外アクセスをすると例外(subscript)が投げられる *)
uncaught exception Subscript [subscript out of bounds]
  raised at: stdIn:18.1-18.6

- A.sub (a0, 5);  (* 範囲外アクセスをすると例外(subscript)が投げられる *)
uncaught exception Subscript [subscript out of bounds]
  raised at: stdIn:22.1-22.6

範囲外にアクセスすると必ず例外が投げられます.

更新

ここまでは違いはありませんでしたが, この部分が大きく異なります.
Vectorはimmutableなのでupdateを呼び出すと, 指定した要素が書き換わったvectorが新たに構築されて返りますが,
Arrayはupdate関数が直接渡した値を書き換えます.

Vector
- v0;
val it = #[1,2,3,4,5] : int vector
- V.update (v0, 0, 10); (* 0番目の要素を10したvectorを新たに作る *)
val it = #[10,2,3,4,5] : int vector
- v0; (* 元の値は変わっていない *)
val it = #[1,2,3,4,5] : int vector
Array
- a0;
val it = [|1,2,3,4,5|] : int array
- V.update (v0, 0, 10); (* 0番目の要素を10に書き換える *)
val it = #[10,2,3,4,5] : int vector
- A.update (a0, 0, 10);
val it = () : unit (* unitが戻ってくる *)
- a0;
val it = [|10,2,3,4,5|] : int array (* 値が書き換わった! *)

op=による比較

Vectorは格納している値が全て等しいかをチェックしますが,
Arrayの場合は同一の実体を比較したときのみtrueとなります.

- val xs0 = V.tabulate (5, fn x=>x);
val xs0 = #[0,1,2,3,4] : int vector
- val xs1 = V.tabulate (5, fn x=>x);
val xs1 = #[0,1,2,3,4] : int vector
- xs0 = xs1;
val it = true : bool    (* 内容が等しければtrue *)
- val xs0 = Array.array (10, 1);
- val xs1 = xs0;        (* 直接コピーすると *)
- xs0 = xs1
val it = true : bool    (* 等しい *)

- val xs0 = Array.array (10, 1);
- val xs1 = Array.array (10, 1); (* 別々に構築すると *)
- xs0 = xs1
val it = false : bool (* 格納している値は等しいけど実体が異なる *)

Unsafe Vector/Array

SML/njにはUnsafe.Vector/Arrayというstructureも用意されています.

signature UNSAFE_VECTOR = sig
    val sub : ('a vector * int) -> 'a
    val create : (int * 'a list) -> 'a vector 
end
signature UNSAFE_ARRAY = sig
    val sub : ('a array * int) -> 'a
    val update : ('a array * int * 'a) -> unit
    val create : (int * 'a) -> 'a array
end

これらはそれぞれ, VectorとArrayから境界チェックを無くしたものなので,
(Unsafeじゃない方の)Vector/Arrayの値に適用することもできます.
「できます」というかそのためのインターフェースですね.

- UnsafeVector.sub (v0, 3);    (* Vectorにunsafe関数を適用 *)
val it = 4 : int

- UnsafeArray.sub (a0, 4);     (* Vectorにunsafe関数を適用 *)
val it = 5 : int

まとめ

  • ランダムアクセスが欲しいならとりあえずVector
  • コピーが問題になるくらいでかい, または頻繁にかつ要素ごとに更新したいならArray
  • アクセスの境界チェックが問題になるほど切羽詰ってるならUnsafe Vector/Array