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)
Then I changed my list to set:
case class MyClassSet(s: Set[Any])
val mySet = Set(("this", false)) // Set[(String, Boolean)]
val setFails = MyClassSet(mySet)
At this point, creating an object of MyClassSet type is no longer ok with me passing a Set as the argument, even when it accepts a Set of Any. Now, it got a bit confusing when the following worked (note that the set is "the same" as the previous mySet):
val setWorks1 = MyClassSet(Set(("this", false)))
I believe that the simple explanation is that the compiler is inferring mySet val as a Set[(String, Boolean)], but when I instantiate it directly in the arguments list of setWorks1, because it accepts a Set[Any], the compiler is inferring it as a Set[Any]. This makes the first example fail and the second one pass. These ones also work, which points to the previous being correct:
val setWorks2 = MyClassSet(mySet.toSet[Any])
val mySetOfAny: Set[Any] = Set(("this", false), ("that", true), ("other", false))
val setWorks3 = MyClassSet(mySetOfAny)
The actual error shown by the compiler is:
Error:(15, 55) type mismatch;
found : Set[(String, Boolean)]
required: Set[Any]
Note: (String, Boolean) <: Any, but trait Set is invariant in type A.
You may wish to investigate a wildcard type such as `_ <: (...)
Lists and Sets are defined as follows:
type List[+A] = scala.collection.immutable.List[A]
type Set[A] = immutable.Set[A]
- Is this difference in the type variance which allows me to pass a List of a "more restricted type than Any" as the argument but not a in the case of Set?
- Is this difference only preventing the casting or conversion between types?
- Is this mostly a compiler "limitation" or an expected property of an invariant type?
- Are there any other differences between invariant types "in practice" or do they boil down to casting such as this?