私は Scala でコレクションをいじっていますが、可変コレクションは不変として定義され、不変コレクションは共変として定義されていることがわかりました。Scalaの分散と可変性/不変性の関係は何ですか?
class Array[T]
class List[+T]
私は Scala でコレクションをいじっていますが、可変コレクションは不変として定義され、不変コレクションは共変として定義されていることがわかりました。Scalaの分散と可変性/不変性の関係は何ですか?
class Array[T]
class List[+T]
SIAで簡単な説明を見つけました。次はそこからまっすぐです。
可変オブジェクトは不変である必要がある 共変でも反変でもない場合、型パラメーターは不変です。すべての Scala ミュータブル コレクション クラスは不変です。変更可能なオブジェクトが不変である必要がある理由を、例で説明できます。ListBuffer は可変であるため、次のように不変として宣言されます。
final class ListBuffer[A] ...{ ... }
不変として宣言されているため、ListBuffer をある型から別の型に割り当てることはできません。次のコードは、コンパイル エラーをスローします。
scala> val mxs: ListBuffer[String] = ListBuffer("pants")
mxs: scala.collection.mutable.ListBuffer[String] =
ListBuffer(pants)
scala> val everything: ListBuffer[Any] = mxs
<console>:6: error: type mismatch;
found : scala.collection.mutable.ListBuffer[String]
required: scala.collection.mutable.ListBuffer[Any]
val everything: ListBuffer[Any] = mxs
String は scala.Any のサブタイプですが、Scala ではすべてに mx を割り当てることはできません。理由を理解するために、ListBuffer が共変であり、次のコード スニペットがコンパイルの問題なく動作すると仮定します。
scala> val mxs: ListBuffer[String] = ListBuffer("pants")
mxs: scala.collection.mutable.ListBuffer[String] =
ListBuffer(pants)
scala> val everything: ListBuffer[Any] = mxs
scala> everything += 1
res4: everything.type = ListBuffer(1, pants)
問題を見つけることができますか?すべての型が Any であるため、整数値を文字列のコレクションに格納できます。これは起こるのを待っている災害です。それはまさに Java 配列に起こることです。この種の問題を回避するには、変更可能なオブジェクトを不変にすることを常にお勧めします。次の質問は、コレクションの不変オブジェクトの場合に何が起こるかです。不変オブジェクトの場合、共分散はまったく問題にならないことがわかります。ListBuffer を不変の List に置き換えると、List[String] のインスタンスを取り、それを List[Any] に問題なく割り当てることができます。
scala> val xs: List[String] = List("pants")
xs: List[String] = List(pants)
scala> val everything: List[Any] = xs
everything: List[Any] = List(pants)
この割り当てが安全である唯一の理由は、List が不変であるためです。xs List に 1 を追加すると、Any 型の新しい List が返されます。
scala> 1 :: xs
res5: List[Any] = List(1, pants)
繰り返しになりますが、cons(::) メソッドは常に新しい List を返し、その型は List 内の要素の型によって決定されるため、この追加は安全です。整数値と参照値を格納できる型は scala.Any だけです。これは、ミュータブル/イミュータブル オブジェクトを扱う際の型の差異について覚えておくべき重要なプロパティです。
反変性を理解する最善の方法は、反変性がない場合に生じる問題を理解することです。次の Java コード例で問題を見つけてみてください。
Object[] arr = new int[1];
arr[0] = "Hello, there!";
文字列を整数配列に割り当てることになります。Java は、ArrayStoreException をスローすることにより、実行時にこのエラーをキャッチします。Scala は、パラメーターの型を強制的に反変または不変にすることで、コンパイル時にこれらの種類のエラーを停止します。
お役に立てれば。
型パラメーターが共変の位置にのみ出現する場合にのみ、型を共変としてマークできます。一般に、これは、クラス/特性/オブジェクトにバリアント型の値を返すメソッドがあるが、バリアント型のパラメーターを持つメソッドがないことを意味します。可変コレクションには、常にバリアント型のパラメーターを持つメソッドがありますupdate
。Array
Array[+T] を宣言できたらどうなるか想像してみてください:
val as = Array[String]("a string")
// this statement won't typecheck in actual Scala
val aa: Array[AnyRef] = as
aa(0) = ("I'm a string...", "but this tuple itself isn't!")
// Tuples don't have a substring method, so this would fail at run-time
// if the compiler allowed this code to compile.
as(0).substring(0)