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?
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]