Почему массивы инвариантны, но списки ковариантны?

например. почему

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

работает, но

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

не работает (поскольку массивы являются инвариантными). Каков желаемый эффект от этого дизайнерского решения?

Ответ 1

Потому что иначе это нарушит безопасность типов. Если нет, вы сможете сделать что-то вроде этого:

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

и компилятор не может его поймать.

С другой стороны, списки являются неизменяемыми, поэтому вы не можете добавить то, что не является Int

Ответ 2

Это потому, что списки неизменяемы, а массивы изменяемы.

Ответ 3

Разница в том, что List являются неизменяемыми, а Array являются изменяемыми.

Чтобы понять, почему изменчивость определяет дисперсию, подумайте о создании измененной версии List - позвоните ей MutableList. Мы также будем использовать некоторые типы примеров: базовый класс Animal и 2 подкласса с именем Cat и Dog.

trait Animal {
  def makeSound: String
}

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

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

Обратите внимание, что Cat имеет еще один метод (jump), чем 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 запись такого кода предотвращается, потому что компилятор будет жаловаться.

Ответ 4

Обычный ответ, который следует дать, заключается в том, что изменчивость в сочетании с ковариацией приведет к нарушению безопасности типов. Для коллекций это можно рассматривать как фундаментальную истину. Но теория действительно применима к любому родовому типу, а не только к коллекциям типа List и Array, и нам не нужно вообще пытаться рассуждать о изменчивости.

Реальный ответ связан с тем, как типы функций взаимодействуют с подтипированием. Короче говоря, если параметр типа используется как возвращаемый тип, он ковариантен. С другой стороны, если параметр типа используется как тип аргумента, он контравариантен. Если он используется как возвращаемый тип и как тип аргумента, он является инвариантным.

Посмотрите на документацию для Array[T]. Два очевидных метода для поиска - для поиска и обновления:

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

В первом методе T есть возвращаемый тип, а во втором T - тип аргумента. Правила дисперсии диктуют, что T должен быть инвариантным.

Мы можем сравнить документацию для List[A], чтобы понять, почему она ковариантна. Смутно мы находим эти методы, которые аналогичны методам Array[T]:

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

Так как A используется как возвращаемый тип, а как тип аргумента, мы ожидаем, что A будет инвариантным, так же как T для Array[T]. Однако, в отличие от Array[T], документация лежит нам о типе ::. Ложь достаточно хороша для большинства вызовов этого метода, но недостаточно хороша, чтобы решить дисперсию A. Если мы расширим документацию для этого метода и нажмем "Полная подпись", нам будет показана правда:

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

Итак, A фактически не отображается как тип аргумента. Вместо этого B (который может быть любым супертипом A) является типом аргумента. Это не помещает никаких ограничений на A, так что это действительно может быть ковариантным. Любой метод на List[A], который имеет A как тип аргумента, является аналогичной ложью (мы можем сказать, потому что эти методы отмечены как [use case]).