Почему пример не компилируется, иначе как работает (co-, contra- и in-) дисперсия?

StackOverflow https://stackoverflow.com/questions/663254

Вопрос

Следуя дальше от этот вопрос, может ли кто-нибудь объяснить следующее в Scala:

class Slot[+T] (var some: T) { 
   //  DOES NOT COMPILE 
   //  "COVARIANT parameter in CONTRAVARIANT position"

}

Я понимаю разницу между +T и T в объявлении типа (он компилируется, если я использую T).Но тогда как на самом деле написать класс, который является ковариантным по своему параметру типа, не прибегая к созданию вещи непараметризованный?Как я могу гарантировать, что следующее может быть создано только с помощью экземпляра T?

class Slot[+T] (var some: Object){    
  def get() = { some.asInstanceOf[T] }
}

Редактировать - теперь сводим это к следующему:

abstract class _Slot[+T, V <: T] (var some: V) {
    def getT() = { some }
}

это все хорошо, но теперь у меня есть два параметра типа, где мне нужен только один.Я переформулирую вопрос таким образом:

Как я могу написать неизменяемый Slot класс, который является ковариантный в своем роде?

ПРАВКА 2:Да!Я использовал var и не val.Вот что я хотел сделать:

class Slot[+T] (val some: T) { 
}
Это было полезно?

Решение

В общем, a ковариантный параметр type - это тот, который может изменяться в меньшую сторону по мере того, как класс является подтипом (альтернативно, может изменяться в зависимости от подтипа, отсюда и префикс "co-").Более конкретно:

trait List[+A]

List[Int] является подтипом List[AnyVal] потому что Int является подтипом AnyVal.Это означает, что вы можете предоставить экземпляр List[Int] когда значение типа List[AnyVal] ожидается.Это действительно очень интуитивно понятный способ работы дженериков, но оказывается, что он несостоятелен (нарушает систему типов) при использовании при наличии изменяемых данных.Вот почему дженерики инвариантны в Java.Краткий пример необоснованности использования Java-массивов (которые ошибочно ковариантны):

Object[] arr = new Integer[1];
arr[0] = "Hello, there!";

Мы только что присвоили значение типа String к массиву типа Integer[].По причинам, которые должны быть очевидны, это плохая новость.Система типов Java фактически допускает это во время компиляции.JVM "услужливо" выдаст ArrayStoreException во время выполнения.Система типов Scala предотвращает эту проблему, поскольку параметр type в Array класс инвариантен (объявление является [A] вместо того , чтобы [+A]).

Обратите внимание, что существует другой тип дисперсии , известный как контравариантность.Это очень важно, поскольку объясняет, почему ковариация может вызвать некоторые проблемы.Контравариантность - это буквально противоположность ковариации:параметры варьируются вверх с подтипом.Это гораздо менее распространенный метод, отчасти потому, что он настолько нелогичен, хотя у него есть одно очень важное применение:функции.

trait Function1[-P, +R] {
  def apply(p: P): R
}

Обратите внимание на "-" дисперсионная аннотация на P введите параметр.Это заявление в целом означает, что Function1 является контравариантным в P и ковариантен в R.Таким образом, мы можем вывести следующие аксиомы:

T1' <: T1
T2 <: T2'
---------------------------------------- S-Fun
Function1[T1, T2] <: Function1[T1', T2']

Обратите внимание на это T1' должен быть подтипом (или тем же типом) T1, тогда как для T2 и T2'.На английском языке это можно прочитать как следующее:

Функция A является подтипом другой функции B если тип параметра A является супертипом параметра type of B в то время как возвращаемый тип A является подтипом возвращаемого типа B.

Обоснование этого правила оставлено в качестве упражнения для читателя (подсказка:подумайте о разных случаях, поскольку функции имеют подтипы, как в моем примере с массивом выше).

С вашими новыми знаниями о со- и контравариантности вы должны быть в состоянии понять, почему следующий пример не будет скомпилирован:

trait List[+A] {
  def cons(hd: A): List[A]
}

Проблема в том, что A является ковариантным, в то время как cons функция ожидает, что ее параметр типа будет равен инвариантный.Таким образом, A меняет направление не в ту сторону.Достаточно интересно, что мы могли бы решить эту проблему, сделав List контравариантный в A, но тогда возвращаемый тип List[A] было бы недействительным, поскольку cons функция ожидает, что ее возвращаемый тип будет ковариантный.

У нас здесь только два варианта: а) сделать A инвариантный, теряющий приятные, интуитивно понятные свойства подтипа ковариации, или б) добавить параметр локального типа в cons метод, который определяет A в качестве нижней границы:

def cons[B >: A](v: B): List[B]

Теперь это действительно так.Вы можете себе это представить A меняется в сторону уменьшения, но B способен изменяться в сторону увеличения по отношению к A с тех пор как A является его нижней границей.С помощью этого объявления метода мы можем иметь A будьте ковариантны, и все получится.

Обратите внимание, что этот трюк работает только в том случае, если мы возвращаем экземпляр List который специализируется на менее специфичном типе B.Если вы попытаетесь сделать List изменяемый, все рушится, так как в конечном итоге вы пытаетесь присвоить значения типа B к переменной типа A, который запрещен компилятором.Всякий раз, когда у вас есть изменчивость, вам нужно иметь какой-то мутатор, для которого требуется параметр метода определенного типа, который (вместе с средством доступа) подразумевает инвариантность.Ковариация работает с неизменяемыми данными, поскольку единственной возможной операцией является средство доступа, которому может быть присвоен ковариантный возвращаемый тип.

Другие советы

@Daniel объяснил это очень хорошо.Но если вкратце объяснить, если это было разрешено:

  class Slot[+T](var some: T) {
    def get: T = some   
  }

  val slot: Slot[Dog] = new Slot[Dog](new Dog)   
  val slot2: Slot[Animal] = slot  //because of co-variance 
  slot2.some = new Animal   //legal as some is a var
  slot.get ??

slot.get затем выдаст ошибку во время выполнения, так как не удалось преобразовать Animal к Dog (да!).

В общем, изменчивость не сочетается с ковариацией и контрвариантностью.Именно по этой причине все коллекции Java инвариантны.

Видеть Скала на примере, стр. 57+ для полного обсуждения этого вопроса.

Если я правильно понимаю ваш комментарий, вам нужно перечитать отрывок, начиная с нижней части страницы 56 (по сути, то, о чем, я думаю, вы просите, не является типобезопасным без проверок во время выполнения, чего Scala не делает, так что вам не повезло).Перевод их примера для использования вашей конструкции:

val x = new Slot[String]("test") // Make a slot
val y: Slot[Any] = x             // Ok, 'cause String is a subtype of Any
y.set(new Rational(1, 2))        // Works, but now x.get() will blow up 

Если вы чувствуете, что я не понимаю ваш вопрос (вероятно), попробуйте добавить дополнительные пояснения/контекст к описанию проблемы, и я попробую еще раз.

В ответ на ваше редактирование:Неизменяемые слоты — это совсем другая ситуация...* :) * Надеюсь, приведенный выше пример помог.

Вам необходимо применить нижнюю границу для параметра.Мне трудно запомнить синтаксис, но я думаю, что это будет выглядеть примерно так:

class Slot[+T, V <: T](var some: V) {
  //blah
}

Примеры Scala немного сложны для понимания, несколько конкретных примеров помогли бы.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top