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

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] , то вы можете сделать следующее:

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
Таким образом, компилятор Scala защищает нас от таких ошибок, ограничивая использование параметров контравариантного типа в ковариантном положении. Для вашего второго вопроса давайте также рассмотрим пример. Рассмотрим, что у нас есть функция:
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] - например, a Cat <: Animal, so List[Cat] <: List[Animal]
  • контравариантность: T <: U -> M[T] >: M[U] - например, a Cat <: Animal, so Function1[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, будет знать, как обращаться с a Tennis, но обратное неверно. Например:

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.