While doing a refactor in a scala application I came across a situation where changing from List to Set raised a question which I didn't have before. I have some idea about variance, but I would like to understand what does it mean for the compiler exactly.
I had something similar to this, which compiles and works just fine:
case class MyClassList(s: List[Any])
val myList = List(("this", false)) // List[(String, Boolean)]
val listWorks = MyClassList(myList)
case class MyClassSet(s: Set[Any])
val mySet = Set(("this", false)) // Set[(String, Boolean)]
val setFails = MyClassSet(mySet)
val setWorks1 = MyClassSet(Set(("this", false)))
val setWorks2 = MyClassSet(mySet.toSet[Any])
val mySetOfAny: Set[Any] = Set(("this", false), ("that", true), ("other", false))
val setWorks3 = MyClassSet(mySetOfAny)
Error:(15, 55) type mismatch;
found : Set[(String, Boolean)]
Note: (String, Boolean) <: Any, but trait Set is invariant in type A.
You may wish to investigate a wildcard type such as `_ <: (...)
type List[+A] = scala.collection.immutable.List[A]
type Set[A] = immutable.Set[A]
1) It's explained here: Why is Scala's immutable Set not covariant in its type?
Basically, Set[T] is also a
Function1[T, Boolean]. The signature of
[-In, +Out], so
T couldn't be both
-T at the same time as scala doesn't allow bivariance (it would significantly weaken type system).
2) You can easily cast it using
.toSet[Any] (which is a wrapper over
asInstanceOf). There is also a way to skip variance check.
3, 4) It's expected property of generic (polymorphic) types. They can be invariant/covariant/contravariant (not only) and it's formally described by simple rules. You can read explanation here: http://stackoverflow.com/a/27627891/1809978