DEV Community

Vitor Nunes
Vitor Nunes

Posted on • Updated on

Contravariance in the real world

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 = ???
}
Enter fullscreen mode Exit fullscreen mode

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:

Waste containers

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 = ???
}
Enter fullscreen mode Exit fullscreen mode

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 = ???
}
Enter fullscreen mode Exit fullscreen mode

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 = ???
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
courier10pt profile image
Bob van Hoove

Thanks for bringing this up, it never quite stuck with me.

Here's a C# example using the 'accepts' phrasing:

// Covariant:
//
//         generic    (accepts)   specific
//
IEnumerable<object> objects = new string[] { "a", "b", "c" }; 


// Contravariant:
//
//    specific      (accepts)       generic
//
Action<string> printIt = new Action<object>(o => Console.WriteLine(o));

Pretty much copied from MSDN : Covariance and Contravariance FAQ

Collapse
 
vitornovictor profile image
Vitor Nunes

I'm glad it helped :)
Thanks for sharing this example!

Collapse
 
tenhobi profile image
Honza Bittner

This article helped me A LOT! Thanks. Awesome example.

Collapse
 
toniogela profile image
Antonio Gelameris

Brilliant!

Collapse
 
theodesp profile image
Theofanis Despoudis

Epic I think that made it clear