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