Контравариантность и ковариантность в Scala
abstract class Bhanu[-A] { val m:List[A] }
Дает
error: contravariant type A occurs in covariant position in type => List[A] of value m
abstract class Bhanu[-A] { val m:List[A] }
Тогда как
abstract class Bhanu[+A] { val m:List[A] }
Дает
defined class Bhanu
Я не в состоянии разобраться в этой концепции относительно того, почему она терпит неудачу для контравариантности, в то время как она преуспевает для ковариации.
Во-вторых (из другого примера),
Что конкретно означает это утверждение?
Function1[Sport,Int] <: Function1[Tennis,Int] since Tennis <: Sport
Мне кажется, это противоречит интуиции, не должно ли это быть следующим?
Function1[Tennis,Int] <: Function1[Sport,Int] since Tennis <: Sport
2 ответа:
Давайте посмотрим на первый пример, который вы упомянули. Рассмотрим, что мы имеем:
class Fruit class Apple extends Fruit class Banana extends Fruit class Bhanu[-A](val m: List[A]) // abstract removed for simplicityТак как
Bhanuявляется контраватиантомBhanu[Fruit] <: Bhanu[Apple], то вы можете сделать следующее:Таким образом, компилятор Scala защищает нас от таких ошибок, ограничивая использование параметров контравариантного типа в ковариантном положении. Для вашего второго вопроса давайте также рассмотрим пример. Рассмотрим, что у нас есть функция:val listOfApples = new List[Apple](...) val listOfFruits = listOfApples // Since Lists are covariant in Scala val a: Bhanu[Fruit] = new Bhanu[Fruit](listOfFruits) val b: Bhanu[Banana] = a // Since we assume Bhanu is contravariant val listOfBananas: List[Banana] = b.m val banana: Banana = listOfBananas(0) // TYPE ERROR! Here object of type Banana is initialized // with object of type Appleval func1: Function1[Tennis,Int] = ...Если
Function1[Tennis,Int] <: Function1[Sport,Int]гдеTennis <: Sportкак вы предложили, то мы можем сделать следующее: следующее:val func2: Function1[Sport,Int] = func1 val result: Int = func2(new Swimming(...)) // TYPE ERROR! Since func1 has argument // of type Tennis.Но если мы сделаем
Function1контравариантным в его аргументе такFunction1[Sport,Int] <: Function1[Tennis,Int], гдеTennis <: Sportчем:val func1: Function1[Tennis,Int] = ... val func2: Function1[Sport,Int] = func1 // COMPILE TIME ERROR!И все прекрасно для обратного случая:
val func1: Function1[Sport,Int] = ... val func2: Function1[Tennis,Int] = func1 // OK! val result1: Int = func1(new Tennis(...)) // OK! val result2: Int = func2(new Tennis(...)) // OK!Функции должны быть контравариантными по типу аргумента и ковариантными по типу результата:
trait Function1[-T, +U] { def apply(x: T): U }
Ответ Дколмакова хорошо объясняет, почему этот конкретный пример не будет работать. Возможно, поможет и более общее объяснение.
Что значит для конструктора типа, функции или признака быть дисперсионным? Согласно определению в Википедии :
Теперь, что такое упорядочение по типам? и что в этом мире означает сохранение или изменение порядка? Это означает, что для любого типаВ системе типов языка программирования, правила набора текста или конструктор типа:
Ковариант: если он сохраняет упорядочение типов (≤) , который упорядочивает типы от более специфических к более общим;
Контравариант: если он отменяет этот порядок;
Инвариантно или не инвариантно, если ни то, ни другое не применимо.
TиUсуществует либо отношение где:
- ковариация:
T <: U->M[T] <: M[U]- например, aCat <: Animal, soList[Cat] <: List[Animal]- контравариантность:
T <: U->M[T] >: M[U]- например, aCat <: Animal, soFunction1[Cat, Unit] >: Function1[Animal, Unit]Или инвариантны, если между ними нет связи.
Обратите внимание, какковариация сохраняет порядок между типами, так какCatвыводитAnimal. Теперь обратите внимание, какконтравариантность меняет порядок, так как теперьFunction0[Animal, Unit]выводитFunction0[Cat, Unit].Как мы можем это принять понятие дисперсии в нашу пользу? Основываясь на этих правилах, мы можем обобщить совместимость присваиваний между конструкторами типов! Хорошие примеры:
List[A],Option[A]иFunction1[-T, +U](или любойFunctionNдействительно).Возьмем для примера a
Function1[-T, +U](T => U) который имеет как ковариантный, так и контравариантный параметр.Почему входной параметр типа является контравариантным, а выходной-ковариантным? Во-первых, согласно аксиомам, определенным выше, мы можем видеть, что:
Function1[Sport,Int] <: Function1[Tennis,Int]В входной параметр типа меняет отношение на типы, так как обычно
Tennis <: Sport, но здесь оно противоположно. Почему это так? потому что любая функция, принимающая в себяSport, будет знать, как обращаться с aTennis, но обратное неверно. Например:Но будет ли функция, ожидающаяval sportFunc: (Sport => Int) = ??? val tennisFunc: (Tennis => Int) = sportFunc val result = tennisFunc(new Tennis())Tennis, знать, как обращаться с любымSport? Конечно, нет:val tennisFunc: (Tennis => Int) = ??? val sportFunc: (Sport => Int) = tennisFunc // The underlying function needs to deal with a Tennis, not a `FootBall`. val result = sportFunc(new FootBall())Противоположное верно относительно выходных типов, которые являются ковариантными, любой ожидающий
Sportкак возвращаемый тип может иметь дело сTennis, илиFootBall, илиVollyBall.