Contravariance hurts the brain! The definition is straightforward, but applying it to the real world doesn’t make much sense. How come Container[Waste]
be a subtype of Container[Paper]
when Paper
is a more specialized type of Waste
?
The key to understanding contravariance is to stop thinking of types in terms of "is a more specialized type" and switch the focus to the idea of acceptance.
Visualize the following scenario in Scala:
class Waste
class Paper extends Waste
class Glass extends Waste
class Container[T] {
//...
}
object Office {
def setPaperContainer(container: Container[Paper]): Unit = ???
def setGlassContainer(container: Container[Glass]): Unit = ???
}
The Office
only accepts specific types of containers:Container[Paper]
and Container[Glass]
. But that’s not practical! What happens, in reality, is to have the same type of container with different colors:
They are not specialized as the example suggests.
The following expresses better what happens in reality:
object Office {
def setPaperContainer(container: Container[Waste]): Unit = ???
def setGlassContainer(container: Container[Waste]): Unit = ???
}
But, what if policies in the office require a particular container for Paper
that shreds everything? Would it make sense to let Container
be covariant? Could the Office
accept Container[Waste]
, or any other more specialized type for both Paper
and Glass
?
class Container[+T] { //<---- Covariant
//...
}
object Office {
def setPaperContainer(container: Container[Waste]): Unit = ???
def setGlassContainer(container: Container[Waste]): Unit = ???
}
The answer is NO. Otherwise, the Office
would accept a shredder as a Container[Glass]
and the result would be messy!
So, how to express that a Container
can be either the basic type or only specialized for a given type? In other words, how to accept a Container[Waste]
and a Container[Paper]
for Paper
, and just accept a Container[Waste]
and Container[Glass]
for Glass
?
Using contravariance!
class Container[-T] {//<---- Contravariant
//...
}
object Office {
def setPaperContainer(container: Container[Paper]): Unit = ???
def setGlassContainer(container: Container[Glass]): Unit = ???
}
By making Container
contravariant, the Office
accepts a Container[Waste]
for both Paper
and Glass
, or a specialized type for each one. And it doesn't accept a shredder for Glass
, or a specialized Glass
container for Paper
.
Don't crush glasses in a paper shredder. Use contravariance!
Top comments (5)
Thanks for bringing this up, it never quite stuck with me.
Here's a C# example using the 'accepts' phrasing:
Pretty much copied from MSDN : Covariance and Contravariance FAQ
I'm glad it helped :)
Thanks for sharing this example!
This article helped me A LOT! Thanks. Awesome example.
Brilliant!
Epic I think that made it clear