pr1001
pr1001

Reputation: 21962

Scala upper type bounds and parent classes

I really like using upper type bounds to give some flexibility to what my constructions can take. However, I really don't know any of the principles behind it as I find with the following code:

object BoundsTest {
  abstract trait Service
  class Collection[T <: Service] extends collection.mutable.HashMap[Symbol, collection.mutable.Set[T]] with collection.mutable.MultiMap[Symbol, T]
  type Actives[T <: Service] = collection.mutable.HashMap[Symbol, T]
  class Library[T <: Service](collection: Collection[T], actives: Actives[T])
  private val libraries = new collection.mutable.HashMap[Symbol, Library[Service]]
  def setLibrary[T <: Service](name: Symbol, library: Library[T]) {
    libraries += name -> library
  }
}

I'm trying to that my classes can use an subclass of Service as long as it's consistent. However, this doesn't work:

$ scalac test.scala 
test.scala:10: error: type mismatch;
 found   : com.bubblefoundry.BoundsTest.Library[T]
 required: com.bubblefoundry.BoundsTest.Library[com.bubblefoundry.BoundsTest.Service]
Note: T <: com.bubblefoundry.BoundsTest.Service, but class Library is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
    libraries += name -> library
                         ^

The problem is, I think, in how (and when?) I define libraries, as if I make the following changes everything compiles successfully:

// private val libraries = new collection.mutable.HashMap[Symbol, Library[Service]]
def setLibrary[T <: Service](name: Symbol, library: Library[T]) {
  new collection.mutable.HashMap[Symbol, Library[T]] += name -> library
}

How can I declare a libraries HashMap such that it has multiple Librarys with different Services? Is it possible to refer to Service here or is that impossible?

Or am I barking completely up the wrong tree? Thanks!

Upvotes: 4

Views: 5663

Answers (2)

oxbow_lakes
oxbow_lakes

Reputation: 134260

As the compile error helpfully says, the class Library is invariant. That is:

Library[S] <: Library[T] iff S <: T

does not hold. This property is called covariance and is a property of generic type parameters. The reason that this is causing a compiler error is that your Map expects Library[Service] as its value type and you are trying to add a Library[T] (which, due to the lack of covariance, is not a Library[Service], even though T <: Service)

If your Library class is immutable, then you should be able to add a + to the type parameter to indicate to scalac that Library is covariant in its type.

Upvotes: 5

Heiko Seeberger
Heiko Seeberger

Reputation: 3722

In Scala parameterized types are invariant by default, e.g. for Cage[A] a Cage[Bird] is not a Cage[Animal]. But you can make a type covariant or even contravariant by means of variance declarations, e.g. Cage[+A](a: A), and this is what the compiler tries to tell you in the error message.

Now it is not always possible to make a type parameter covariant. This will only work if the type variable is only used in so called positive occurrences. Or put another (not 100% correct) way, if your class is immutable. In your case, it would work. So all you have to do is add a + to the definition of Library:

class Library[+T <: Service](collection: Collection[T], actives: Actives[T])

Upvotes: 11

Related Questions