Sunday, 25 December 2011

Scala for Java Developers: Generic and Parametrized Types – Part II

In previous post we've talked about the basics of scala parametrized types. Let's now talk about some advanced stuff.

Here are few class we are going to use in our examples:
class Home[T]{
  private var inside = Set[T]()
  def enter(entering : T) = inside += entering
  def whoIsInside = inside
}

class Creature
class Human extends Creature


Understanding Covariance

Let’s say, we wanted our Home class to accommodate any Creature, i.e.
var house : Home[Creature] = new Home[Creature]
house enter(new Human)


What if we tried to assign Home[Human] to variable of type Home[Creature]?
house = new Home[Human]
error: type mismatch; found : Home[Human] required: Home[Creature]


Ok, Home[Human] is not a subclass of Home[Creature]. To fix the problem we would have to change our Home declaration to: class Home[+T]. This tells the compiler that class Home[Human] is a subclass of Home[Creature], because Human is a subclass of Creature. This is what it’s called covariance, and type parameter T is covariant.

Covariance of List

Have a look at the List class. It is declared as List[+A]; this gives you a lot of flexibility when manipulating lists, i.e.

var creatures : List[Creature] = List(new Dog, new Dog)
creatures = List(new Human)

Restrictions of covariance

Unfortunately, if we make our type parameter T covariant (Home[+T]), we will get a nasty compilation error:
error: covariant type T occurs in contravariant position in type T of value entering
         def enter(entering : T) = inside += entering


Let’s explain why, by looking at the following example:
class Dog extends Creature{
  def bark = println("Woof woof")
}

class DogHouse extends Home[Dog]{
  override def enter(entering : Dog) = { 
    entering.bark 
    super.enter(dog) 
  }
}


So far so good, the dog house makes the dog bark when it enters the dog house. Assuming that Home was declared as covariant: Home[+T], and the program compiled without errors - which isn’t true - lets try the following:
var home : Home[Creature] = new DogHouse //this is legal; Home[+T] is covariant
home.enter(new Human) // this seems legal as Human is a subtype of Creature


As far as assigning a DogHouse to the home variable - which is a home for any creature - sounds only interesting, the idea of a human entering a dog house is a bit crazy. More than that, if you look at overridden enter method, you will see, that the DogHouse will try to make our human bark, when he enters it...

Knowledge about parametrizing type is problematic

Clearly we did something wrong. We used the knowledge about the parameter type T in the overridden enter method: we knew that entering parameter type is Dog, so we wanted it to bark.

Compiler detects covariance issues

Scala compiler protects us against similar mistakes by enforcing that, if the type we used to parametrize our class is covariant (+T), then information about the type can not be used within the class. In other words: the parametrized class Home[+T] should be orthogonal to T, or should know nothing about the type T.

Learning more about covariance

You can find lots of examples of covariant types between scala immutable collections. By nature any container like object, which doesn’t care what it’s content is, is a good candidate for a covariant type.


I hope this post was helpful to you. Please let me know what you think about it, and leave a comment.

No comments:

Post a Comment