web-dev-qa-db-ja.com

配列は不変ですが、リストは共変なのはなぜですか?

例えば。どして

val list:List[Any] = List[Int](1,2,3)

動作しますが

val arr:Array[Any] = Array[Int](1,2,3)

失敗します(配列は不変であるため)。この設計決定の背後にある望ましい効果は何ですか?

53
fresskoma

そうでなければ型安全性を損なうからです。そうでない場合は、次のようなことができます。

val arr:Array[Int] = Array[Int](1,2,3)
val arr2:Array[Any] = arr
arr2(0) = 2.54

コンパイラはそれをキャッチできません。

一方、リストは不変であるため、Int以外のものを追加することはできません。

72
Op De Cirkel

これは、リストが不変であり、配列が可変であるためです。

26
sshannin

違いは、Listsは不変であるのに対し、Arraysは可変であるということです。

可変性が分散を決定する理由を理解するには、Listの可変バージョンを作成することを検討してください。これをMutableListと呼びましょう。また、いくつかのサンプルタイプを使用します。基本クラスAnimalと、CatおよびDogという名前の2つのサブクラスです。

trait Animal {
  def makeSound: String
}

class Cat extends Animal {
  def makeSound = "meow"
  def jump = // ...
}

class Dog extends Animal {
  def makeSound = "bark"
}

Catにはjumpよりも1つ多いメソッド(Dog)があることに注意してください。

次に、動物の変更可能なリストを受け入れ、リストを変更する関数を定義します。

def mindlessFunc(xs: MutableList[Animal]) = {
  xs += new Dog()
}

猫のリストを関数に渡すと、恐ろしいことが起こります。

val cats = MutableList[Cat](cat1, cat2)
val horror = mindlessFunc(cats)

不注意なプログラミング言語を使用していた場合、これはコンパイル時に無視されます。それでも、次のコードを使用して猫のリストにアクセスするだけで、私たちの世界は崩壊しません。

cats.foreach(c => c.makeSound)

しかし、これを行うと:

cats.foreach(c => c.jump)

ランタイムエラーが発生します。 Scalaでは、コンパイラーが文句を言うので、そのようなコードを書くことは妨げられます。

6
Yuhuan Jiang

与える通常の答えは、共分散と組み合わされた可変性は型安全性を壊すだろうということです。コレクションの場合、これは基本的な真実と見なすことができます。しかし、この理論は実際にはListArrayのようなコレクションだけでなく、あらゆるジェネリック型に適用され、可変性について考える必要はまったくありません。

本当の答えは、関数型がサブタイプと相互作用する方法に関係しています。簡単に言うと、型パラメーターが戻り値の型として使用されている場合、それは共変です。一方、型パラメーターが引数型として使用される場合、それは反変です。戻り値の型と引数の型の両方として使用される場合、不変です。

Array[T]のドキュメント を見てみましょう。確認する2つの明らかな方法は、ルックアップと更新の方法です。

def apply(i: Int): T
def update(i: Int, x: T): Unit

最初のメソッドではTは戻り値の型であり、2番目のメソッドではTは引数の型です。したがって、分散の規則は、Tが不変でなければならないことを示しています。

List[A] のドキュメントを比較して、共変である理由を確認できます。紛らわしいことに、これらのメソッドはArray[T]のメソッドに類似しています。

def apply(n: Int): A
def ::(x: A): List[A]

Aは戻り値の型と引数の型の両方として使用されるため、AArray[T]の場合と同様に、Tは不変であると予想されます。ただし、Array[T]とは異なり、ドキュメントは::のタイプについて私たちに嘘をついています。嘘は、このメソッドのほとんどの呼び出しには十分ですが、Aの分散を決定するには十分ではありません。このメソッドのドキュメントを展開して[完全な署名]をクリックすると、真実が表示されます。

def ::[B >: A](x: B): List[B]

したがって、Aは実際には引数タイプとして表示されません。代わりに、BAの任意のスーパータイプにすることができます)が引数タイプです。これはAに制限を課さないので、実際には共変である可能性があります。引数の型としてAを持つList[A]のメソッドは、同様の嘘です(これらのメソッドは[use case]としてマークされているためわかります)。