Почему массивы инвариантны, а списки ковариантны? - PullRequest
50 голосов
/ 13 июля 2011

Например, почему

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

работает, но

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

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

Ответы [ 4 ]

72 голосов
/ 13 июля 2011

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

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

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

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

26 голосов
/ 13 июля 2011

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

5 голосов
/ 01 июля 2015

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

Чтобы понять, почему изменчивость определяет дисперсию, рассмотрим создание изменяемой версии 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 голосов
/ 10 марта 2017

Нормальный ответ на этот вопрос заключается в том, что изменчивость в сочетании с ковариацией может нарушить безопасность типов.Для коллекций это может быть принято как фундаментальная истина.Но теория на самом деле применима к любому универсальному типу, а не только к коллекциям, таким как 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]).

...