0

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?

blogscot
  • 41
  • 7
  • 1
    Your example has nothing to do with contravariance, see [Programming in Scala 19.3](http://www.artima.com/pins1ed/type-parameterization.html#19.3) for an in-depth explanation. – kiritsuku May 14 '14 at 11:36

2 Answers2

4

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]
sksamuel
  • 16,154
  • 8
  • 60
  • 108
4

Before answering the question, I need to note two things:

a) you are not using covariance / contravariance in your example, you merely define a lower bound

b) in the title, you imply that this is a Scala concept. Covariance and contravariance are general concepts in object orientation, and have existed long before Scala.

So regarding your original question, how is it useful? It allows you to specify inheritance for parameterized types. For example, if you defined you type parameter as covariant, maybe with Creature as an upper bound, you could express that a dog's home can stand in for a creature's home:

scala> class Home[+T <: Creature]
defined class Home

scala> var home = new Home[Creature]
home: Home[Creature] = Home@46a32efb

scala> home = new Home[Dog]
home: Home[Creature] = Home@1b955e70

So Home[Dog] is a subtype of of Home[Creature] - covariance allows you to express this.

Also note that in your example just making the type parameter covariant would not compile, as you allow entering. The method parameter can not be covariant, as that would break the substitutability. The Scala compiler will detect this for you.

lutzh
  • 4,917
  • 1
  • 18
  • 21