Search code examples
scalacovariancecontravariance

How is Scala's Covariance, Contravariance useful?


I've been learning about scala's use of covariance and contravariance parameterized typing; I'm a bit perplexed by the following example code:

class Thing
class Creature extends Thing
class Human extends Creature
class Dog extends Creature

class Home[T >: Creature] {
  private var inside = Set[T]()

  def enter(entering: T) = inside += entering

  def whoIsInside = inside
}

val house = new Home[Thing]
val cage = new Home[Creature]
house enter new Human
cage enter new Dog

As I understand it, the parameterized class Home uses contravariance with a lower bound of Creature, so

val flat = new Home[Human]

causes a compiler error, which is what I expected. My predicament is, I've created a new 'house' but I can put a 'Human' in it! While this also makes sense because a 'Human' is a 'Thing' I was naively expecting it to fail! Putting aside the mechanics, how is covariance, contravariance useful?


Solution

  • In your example, you can put subtypes of T into the collection of T, because U <: T is also a T. That is not covariance or contravariance.

    Covariance would be when your House[U] is a subtype of a House[T] for U <: T. So if for example you asked for a House[Creature] you could offer a House[Human] if T was covariant. We use the + on a type parameter for covariance.

    Eg,

    class Home[+T]
    val cage: Home[Creature] = new Home[Human]
    

    The most useful example of this is when you use Nil for a List, because Nil is a List[Nothing] and List is covariant. So a List[Nothing] can substitute for any type of List at all.

    Contravariance is the opposite. That a House[U] is a supertype of House[T] if U <: T. We use the - on a type parameter for contravariance.

    Eg,

    class Home[-T]
    val cage: Home[Human] = new Home[Creature]