Reputation: 21962
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 Library
s with different Service
s? 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
Reputation: 134260
As the compile error helpfully says, the class Library
is invariant. That is:
Library[S] <: Library[T]
iffS <: 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
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