Контравариантность и ковариантность в 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 Apple
val 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
.