TL; DR:
Ваш класс Pets
может производить значения типа A
, возвращая переменную-член pet
, поэтому Pet[VeryGeneral]
не может быть подтипом Pet[VerySpecial]
, потому что, когда он производит что-то VeryGeneral
, он не может гарантировать, что он также является экземпляром VerySpecial
. Следовательно, он не может быть контравариантным.
Ваш Pets
класс может потреблять значения типа A
, передавая их в качестве аргументов в add
. Следовательно, Pet[VerySpecial]
не может быть подтипом pet Pet[VeryGeneral]
, потому что он подавится любым вводом, отличным от VerySpecial
. Следовательно, ваш класс не может быть ковариантным.
Остается только одна возможность: Pets
должен быть неизменным в A
.
Иллюстрация: Ковариация против контравариантности:
Я воспользуюсь этой возможностью, чтобы представить улучшенную и значительно более строгую версию этого комикса. Это иллюстрация концепций ковариации и контравариантности для языков программирования с аннотациями подтипов и вариаций на объекте объявления (очевидно, даже Java-разработчики сочли это достаточно поучительным, несмотря на то, что вопрос касался дисперсии использования сайта).
Во-первых, иллюстрация:
![ковариация-контравариантность-комикс](https://i.stack.imgur.com/6MQ16.png)
Теперь более подробное описание с компилируемым кодом Scala.
Объяснение контравариантности (левая часть рисунка 1)
Рассмотрим следующую иерархию источников энергии, от очень общей до очень конкретной:
class EnergySource
class Vegetables extends EnergySource
class Bamboo extends Vegetables
Теперь рассмотрим черту Consumer[-A]
, которая имеет единственный consume(a: A)
-метод:
trait Consumer[-A] {
def consume(a: A): Unit
}
Давайте реализуем несколько примеров этой черты:
object Fire extends Consumer[EnergySource] {
def consume(a: EnergySource): Unit = a match {
case b: Bamboo => println("That's bamboo! Burn, bamboo!")
case v: Vegetables => println("Water evaporates, vegetable burns.")
case c: EnergySource => println("A generic energy source. It burns.")
}
}
object GeneralistHerbivore extends Consumer[Vegetables] {
def consume(a: Vegetables): Unit = a match {
case b: Bamboo => println("Fresh bamboo shoots, delicious!")
case v: Vegetables => println("Some vegetables, nice.")
}
}
object Panda extends Consumer[Bamboo] {
def consume(b: Bamboo): Unit = println("Bamboo! I eat nothing else!")
}
Итак, почему Consumer
должен быть контравариантным в A
? Давайте попробуем создать несколько разных источников энергии, а затем скормить их разным потребителям:
val oilBarrel = new EnergySource
val mixedVegetables = new Vegetables
val bamboo = new Bamboo
Fire.consume(bamboo) // ok
Fire.consume(mixedVegetables) // ok
Fire.consume(oilBarrel) // ok
GeneralistHerbivore.consume(bamboo) // ok
GeneralistHerbivore.consume(mixedVegetables) // ok
// GeneralistHerbivore.consume(oilBarrel) // No! Won't compile
Panda.consume(bamboo) // ok
// Panda.consume(mixedVegetables) // No! Might contain sth Panda is allergic to
// Panda.consume(oilBarrel) // No! Pandas obviously cannot eat crude oil
Результат: Fire
может потреблять все, что GeneralistHerbivore
может потреблять, и, в свою очередь, GeneralistHerbivore
может потреблять все, что Panda
может съесть. Следовательно, до тех пор, пока мы заботимся только о способности потреблять источники энергии, Consumer[EnergySource]
можно заменить там, где требуется Consumer[Vegetables]
, и Consumer[Vegetables]
, где требуется Consumer[Bamboo]
. Следовательно, имеет смысл, что Consumer[EnergySource] <: Consumer[Vegetables]
и Consumer[Vegetables] <: Consumer[Bamboo]
, даже если соотношение между параметрами типа прямо противоположное:
type >:>[B, A] = A <:< B
implicitly: EnergySource >:> Vegetables
implicitly: EnergySource >:> Bamboo
implicitly: Vegetables >:> Bamboo
implicitly: Consumer[EnergySource] <:< Consumer[Vegetables]
implicitly: Consumer[EnergySource] <:< Consumer[Bamboo]
implicitly: Consumer[Vegetables] <:< Consumer[Bamboo]
Объяснение ковариации (правая часть рисунка 1)
Определите иерархию продуктов:
class Entertainment
class Music extends Entertainment
class Metal extends Music // yes, it does, seriously^^
Определите типаж, который может создавать значения типа A
:
trait Producer[+A] {
def get: A
}
Определите различных «источников» / «производителей» разного уровня специализации:
object BrowseYoutube extends Producer[Entertainment] {
def get: Entertainment = List(
new Entertainment { override def toString = "Lolcats" },
new Entertainment { override def toString = "Juggling Clowns" },
new Music { override def toString = "Rick Astley" }
)((System.currentTimeMillis % 3).toInt)
}
object RandomMusician extends Producer[Music] {
def get: Music = List(
new Music { override def toString = "...plays Mozart's Piano Sonata no. 11" },
new Music { override def toString = "...plays BBF3 piano cover" }
)((System.currentTimeMillis % 2).toInt)
}
object MetalBandMember extends Producer[Metal] {
def get = new Metal { override def toString = "I" }
}
BrowseYoutube
- это самый общий источник Entertainment
: он может дать вам практически любой вид развлечения: видео с кошками, жонглирование клоунами или (случайно) немного музыки. Этот общий источник Entertainment
представлен архетипическим шутом на Рисунке 1.
RandomMusician
уже несколько более специализирован, по крайней мере, мы знаем, что этот объект производит музыку (хотя нет никаких ограничений для какого-либо определенного жанра).
Наконец, MetalBandMember
чрезвычайно специализирован: метод get
гарантированно возвращает только очень специфический тип Metal
музыки.
Давайте попробуем получить различные виды Entertainment
из этих трех объектов:
val entertainment1: Entertainment = BrowseYoutube.get // ok
val entertainment2: Entertainment = RandomMusician.get // ok
val entertainment3: Entertainment = MetalBandMember.get // ok
// val music1: Music = BrowseYoutube.get // No: could be cat videos!
val music2: Music = RandomMusician.get // ok
val music3: Music = MetalBandMember.get // ok
// val metal1: Entertainment = BrowseYoutube.get // No, probably not even music
// val metal2: Entertainment = RandomMusician.get // No, could be Mozart, could be Rick Astley
val metal3: Entertainment = MetalBandMember.get // ok, because we get it from the specialist
Мы видим, что все три Producer[Entertainment]
, Producer[Music]
и Producer[Metal]
могут давать какой-то Entertainment
. Мы видим, что только Producer[Music]
и Producer[Metal]
гарантированно произведут Music
. Наконец, мы видим, что только чрезвычайно специализированный Producer[Metal]
гарантированно производит Metal
и ничего больше. Следовательно, Producer[Music]
и Producer[Metal]
можно заменить на Producer[Entertainment]
. Producer[Metal]
можно заменить на Producer[Music]
. В общем, производитель более конкретного вида продукции может быть заменен менее специализированным производителем:
implicitly: Metal <:< Music
implicitly: Metal <:< Entertainment
implicitly: Music <:< Entertainment
implicitly: Producer[Metal] <:< Producer[Music]
implicitly: Producer[Metal] <:< Producer[Entertainment]
implicitly: Producer[Music] <:< Producer[Entertainment]
Отношения подтипов между продуктами такие же, как отношения подтипов между производителями продуктов. Вот что означает ковариация.
Ссылки по теме
Аналогичное обсуждение ? extends A
и ? super B
в Java 8: Статическая функция Java 8 Comparator
comparing()
Классический вопрос «какие параметры типа для flatMap
в моей собственной реализации Either
»: Тип L
появляется в контравариантной позиции в Either[L, R]
person
Andrey Tyukin
schedule
19.02.2018