This is a code snippet from one of the articles that I read regarding contravariance and covariance in scala. However, I fail to understand the error message thrown by the scala compiler "error: covariant type A occurs in contravariant position in type A of value pet2
class Pets[+A](val pet:A) {
def add(pet2: A): String = "done"
}
My understanding of this code snippet is that Pets is covariant and accepts objects that are subtypes of A. However, the function add takes in a parameter of type A only.Being covariant means that the Pets can take parameters of Type A and its subtypes. Then how is this supposed to throw error. From where is the question of contravariance even arising.
Any explanation to the above error message will be highly helpful. Thanks
The contravariant components of a vector are obtained by projecting onto the coordinate axes. The covariant components are obtained by projecting onto the normal lines to the coordinate hyperplanes.
In C#, covariance and contravariance enable implicit reference conversion for array types, delegate types, and generic type arguments. Covariance preserves assignment compatibility and contravariance reverses it.
Covariance. Enables you to use a more derived type than originally specified. You can assign an instance of IEnumerable<Derived> to a variable of type IEnumerable<Base> . Contravariance. Enables you to use a more generic (less derived) type than originally specified.
Covariance can be translated as "different in the same direction," or with-different, whereas contravariance means "different in the opposite direction," or against-different. Covariant and contravariant types are not the same, but there is a correlation between them. The names imply the direction of the correlation.
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: Metal = BrowseYoutube.get // No, probably not even music
// val metal2: Metal = RandomMusician.get // No, could be Mozart, could be Rick Astley
val metal3: Metal = 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]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With