8

考虑以下(使用 Scala 2.8.1 和 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

最后一行失败:

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

更改它以val dogMap = AnimalsMap[Dog](Map())修复它,但不再利用默认参数值。

鉴于 List 对应项按预期工作,为什么默认值被推断为 Map[Nothing,Nothing]?有没有办法创建一个使用maparg 的默认值的 AnimalsMap 实例?


编辑:我已经接受了对我更紧迫的第二个问题的回答,但我仍然想知道为什么Map()在这两种情况下推断的键类型不同:

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

编辑2:似乎类型界限是无关紧要的 - 案例类的任何参数类型都会导致问题:

case class Map3[A](map:Map[String,A] = Map())
val dogs3 = Map3[Dog]() // Does not compile
4

2 回答 2

21

Scala有一个特性,你可以定义一个类在它的泛型参数中是协变/逆变的。

作为协方差的一个例子:很自然地认为 if class Student extends Personthen List[Student]"extends" List[Person]。这是因为每个接受 a 的方法List[Person]在处理对象时应该没有问题List[Student]。这在 Java 中是不可能的(不使该方法也通用)。

逆变是相反的,解释起来有点棘手。当类型应该被推送到泛型类而不是读取(在List[Person]您读取列表的元素时)时,它是必需的。一般的例子是一个函数。函数的参数类型被放入其中,所以如果一个方法需要一个函数Person => String,它不能用函数Student => String调用(它会用一个人调用参数,但它需要一个学生)

Scala 还定义Nothing了隐式扩展所有内容。它是底部类型。因此,对于任何 X.创建List[Nothing]总是“扩展” ,而协方差就是您可以编写.List[X]List()List[Nothing]val x: List[Person] = List()

无论如何,Map 的键类型是不变的。原因是 aMap[A, B]就像一个函数A => B,所以它只能在 中是逆变的A。另一种方法是考虑如果将 a 传递Map[Student, String]给期望的方法会发生什么Map[Person, String],显然它可能会尝试将Person不好的对象放入其中,另一种方法是可以的。另一方面,可以将 Map 视为Iterable[(A, B)],这里它应该在 A 中是协变的。因此它的值是不变的。

结果是您不能将 a 分配给Map[Nothing, Nothing]类型为 的变量Map[String, Animal]Map()创建一个Map[Nothing, Nothing]

编译器会告诉你:

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]()
                       ^
于 2011-05-26T09:37:59.363 回答
4

只需给编译器一点帮助:

case class AnimalsMap[A <: Animal](map:Map[String,A] = Map[String, A]())
                                                          ^^^^^^^^^^^

我将把你的解决方案为什么不起作用的细节留给更熟悉 Scala 类型推断的人......</p>

编辑:请参阅IttayD 的回答,以获得对这种行为的一个很好的解释。

于 2011-05-26T08:37:09.633 回答