Consider the following (tested with Scala 2.8.1 and 2.9.0):
trait Animal
class Dog extends Animal
case class AnimalsList[A <: Animal](list:List[A] = List())
case class AnimalsMap[A <: Animal](map:Map[String,A] = Map())
val dogList = AnimalsList[Dog]() // Compiles
val dogMap = AnimalsMap[Dog]() // Does not compile
The last line fails with:
error: type mismatch;
found : scala.collection.immutable.Map[Nothing,Nothing]
required: Map[String,Main.Dog]
Note: Nothing <: String, but trait Map is invariant in type A.
You may wish to investigate a wildcard type such as `_ <: String`. (SLS 3.2.10)
Error occurred in an application involving default arguments.
val dogMap = AnimalsMap[Dog]() // Does not compile
^
one error found
Changing it to val dogMap = AnimalsMap[Dog](Map())
fixes it, but no longer takes advantage of the default argument value.
Why is the default value being inferred as Map[Nothing,Nothing], given that List counterpart works as expected? Is there a way to create an AnimalsMap instance that uses the default value for the map
arg?
Edit: I've accepted an answer to my more-pressing second question, but I'd still be interested to know why the key type to Map()
is inferred differently between these two cases:
case class AnimalsMap1(map:Map[String,Animal] = Map())
val dogs1 = AnimalsMap1() // Compiles
case class AnimalsMap2[A <: Animal](map:Map[String,A] = Map())
val dogs2 = AnimalsMap2[Dog]() // Does not compile
Edit 2: Seems that type bounds are irrelevant - any parametric type to the case class causes the problem:
case class Map3[A](map:Map[String,A] = Map())
val dogs3 = Map3[Dog]() // Does not compile
Scala has a feature where you can define a class to be covariant / contravariant in its generic parameters.
As an example to covariance: it is natural to think that if class Student extends Person
then List[Student]
"extends" List[Person]
. This is because every method that accepts a List[Person]
should have no problem working with an object List[Student]
. This is not possible in Java (without making the method also generic).
Contravariance is the opposite and is a bit trickier to explain. It is required when the type is supposed to be pushed to the generic class, not read (in List[Person]
you read the elements of the list). The general example is a function. The types of the arguments to a function are put into it, so if a method expects a function Person => String
it can't be called with a function Student => String
(it will call the argument with a person, but it expects a student)
Scala also defines Nothing
to implicitly extend everything. It is the bottom type. So List[Nothing]
always "extends" List[X]
for any X. List()
creates List[Nothing]
and the covariance is why you can write val x: List[Person] = List()
.
Anyways, Map is invariant in its key types. The reason is that a Map[A, B]
is like a function A => B
so it can only be contravariant in A
. Another way is to think what happens if you pass a Map[Student, String]
to a method expecting Map[Person, String]
, obviously it may try to put Person
objects in there which is not good, the other way is OK. On the other hand, a Map can be viewed as Iterable[(A, B)]
, here it should be covariant in A. So it is invariant in its value.
The result is you cannot assign a Map[Nothing, Nothing]
to a variable of type Map[String, Animal]
. Map()
creates a Map[Nothing, Nothing]
The compiler tells you this:
scala> val dogs3 = Map3[Dog]()
<console>:13: error: type mismatch;
found : scala.collection.immutable.Map[Nothing,Nothing]
required: Map[String,Dog]
Note: Nothing <: String, but trait Map is invariant in type A.
You may wish to investigate a wildcard type such as `_ <: String`. (SLS 3.2.10)
Error occurred in an application involving default arguments.
val dogs3 = Map3[Dog]()
^