ScalaのArrayとArrayBuffer
SwiftのArrayがおかしいって話があったので、たぶん、Scalaなら美しいだろうと思って調べてみました。
package s object main extends App { val a = Array(1,2,3) var b = a a(1) = 33 b(0) = 55 b = b :+ 2 var b2 = b :+ 2 println(a.mkString(" . ")) println(b.mkString(" . ")) println(b2.mkString(" . ")) val c = new scala.collection.mutable.ArrayBuffer[Int](1) c += 1 var d = c d += 2 println(c) println(d) }
結果はこんな感じ。
55 . 33 . 3 55 . 33 . 3 . 2 55 . 33 . 3 . 2 . 2 ArrayBuffer(1, 2) ArrayBuffer(1, 2)
ScalaのArrayはimmutableな変数でも値を代入出来ます。
で、追加する時は :+を使って追加しますが、元の配列そのものは変わりません。
なので、中身が変わったおかしいってことはない。
ArrayBufferは完全に中身を共有しているので、
サイズが1としてあって、内部でリアロケーションが起こっても、共有され続ける。
どちらも、訳の分からない事にはならないので、うまく出来てます。
import core.stdc.stdio; void main() { immutable int[] a = [1]; int[] b = cast(int[])a; b[0] = 222; b ~= 333; printf("%d %d %d %d\n", a[0], b[0], a.length, b.length); }
結果
222 222 1 2
DMD64 D Compiler v2.060だと、bの長さが変わると、中身もかわるかもしれないが、配列への追加は必ずコピーを行うとは限りません。と仕様にある。
immutableなリストは一部書き換えは出来ないので、変わらないけど、bを書き換えるとaも変わる。bの長さが変わるとリアロケーションされて、書き換えてもaは変わらない。
仕様もそうなっている。
OCamlの配列も書き換え可能だ。
MLやScalaでは配列は書き換え可能だ。immutableなのは変数そのもので、中身のことではない。
Dのようなネイティブよりの言語はimmutableなリストがないので、immutableな配列は中身も書き換えられないとしているのではあるけど、castして無理矢理取り出せば書き換えられる。という事なのでした。
内部の実装を考えると、immutableってSSA最適化をする場合の書き換えられないレジスタ相当で、書き換え可能名変数はload,storeをする変数と考えるのが実装がシンプルでかつ、最適化するにもやりやすい。
配列の中身を書き換えられないようにする必要性ってあまりないのかなと。配列を使うという事は高速性を求めているということであるし、immutableなデータとしては、ListかMapか、Setを使えば大体事足りるはずだ。
最適化まで考えて仕様を考えれば結構普通なんじゃないのかなとそんな風に思う。
ArrayBufferは良さそうと思うかもしれないけども、ポインタのポインタと2段階のアクセスをするのは高速さを求める場合は若干だが遅くなる。だけど、実際のアロケートされている配列のサイズは大きく取っておけるので、リアロケーションはたまにしか起こらないから速い。追加操作があると便利だという事もあるのだけど、普通つけない機能だろ。っていう感じだ。