TL;DR:
Your Pets
class can produce values of type A
by returning the member variable pet
, so a Pet[VeryGeneral]
cannot be a subtype of Pet[VerySpecial]
, because when it produces something VeryGeneral
, it cannot guarantee that it is also an instance of VerySpecial
. Therefore, it cannot be contravariant.
Your Pets
class can consume values of type A
by passing them as arguments to add
. Therefore a Pet[VerySpecial]
cannot be a subtype of pet Pet[VeryGeneral]
, because it will choke on any input that is not VerySpecial
. Therefore, your class cannot be covariant.
The only remaining possibility is: Pets
must be invariant in A
.
An illustration: Covariance vs. Contravariance:
I'll use this opportunity to present an improved and significantly more
rigorous version of this comic. It is an illustration of the covariance and contravariance
concepts for programming languages with subtyping and declaration-site variance annotations
(apparently, even Java people found it sufficiently enlightening,
despite the fact that the question was about use-site variance).
First, the illustration:
Now a more detailed description with compilable Scala code.
Explanation for Contravariance (left part of Figure 1)
Consider the following hierarchy of energy sources, from very general, to very specific:
class EnergySource
class Vegetables extends EnergySource
class Bamboo extends Vegetables
Now consider a trait Consumer[-A]
that has a single consume(a: A)
-method:
trait Consumer[-A] {
def consume(a: A): Unit
}
Let's implement a few examples of this trait:
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!")
}
Now, why does Consumer
have to be contravariant in A
? Let's try to instantiate
a few different energy sources, and then feed them to various consumers:
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
The outcome is: Fire
can consume everything a GeneralistHerbivore
can consume,
and in turn GeneralistHerbivore
can consume everything a Panda
can eat.
Therefore, as long as we care only about the ability to consume energy sources,
Consumer[EnergySource]
can be substituted where a Consumer[Vegetables]
is required,
and
Consumer[Vegetables]
can be substituted where a Consumer[Bamboo]
is required.
Therefore, it makes sense that Consumer[EnergySource] <: Consumer[Vegetables]
and
Consumer[Vegetables] <: Consumer[Bamboo]
, even though the relationship between
the type parameters is exactly the opposite:
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]
Explanation for Covariance (right part of Figure 1)
Define a hierarchy of products:
class Entertainment
class Music extends Entertainment
class Metal extends Music // yes, it does, seriously^^
Define a trait that can produce values of type A
:
trait Producer[+A] {
def get: A
}
Define various "sources"/"producers" of varying levels of specialization:
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" }
}
The BrowseYoutube
is the most generic source of Entertainment
: it could give you
basically any kind of entertainment: cat videos, juggling clowns, or (accidentally)
some music.
This generic source of Entertainment
is represented by the archetypical jester in the Figure 1.
The RandomMusician
is already somewhat more specialized, at least we know that this object
produces music (even though there is no restriction to any particular genre).
Finally, MetalBandMember
is extremely specialized: the get
method is guaranteed to return
only the very specific kind of Metal
music.
Let's try to obtain various kinds of Entertainment
from those three objects:
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
We see that all three Producer[Entertainment]
, Producer[Music]
and Producer[Metal]
can produce some kind of Entertainment
.
We see that only Producer[Music]
and Producer[Metal]
are guaranteed to produce Music
.
Finally, we see that only the extremely specialized Producer[Metal]
is guaranteed to
produce Metal
and nothing else. Therefore, Producer[Music]
and Producer[Metal]
can be substituted
for a Producer[Entertainment]
. A Producer[Metal]
can be substituted for a Producer[Music]
.
In general, a producer of
a more specific kind of product can be subsituted for a less specialized producer:
implicitly: Metal <:< Music
implicitly: Metal <:< Entertainment
implicitly: Music <:< Entertainment
implicitly: Producer[Metal] <:< Producer[Music]
implicitly: Producer[Metal] <:< Producer[Entertainment]
implicitly: Producer[Music] <:< Producer[Entertainment]
The subtyping relationship between the products is the same as the subtyping relationship between
the producers of the products. This is what covariance means.
Related links
A similar discussion about ? extends A
and ? super B
in Java 8:
Java 8 Comparator
comparing()
static function
Classical "what are the right type parameters for flatMap
in my own Either
implementation" question: Type L
appears in contravariant position in Either[L, R]