有界类型参数化案例类和Scala中的默认参数的问题

时间:2011-05-26 08:25:38

标签: scala case-class default-arguments

考虑以下内容(使用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())会修复它,但不再利用默认参数值。

为什么默认值被推断为Map [Nothing,Nothing],因为List对应物按预期工作?有没有办法创建一个使用map arg?

的默认值的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

2 个答案:

答案 0 :(得分:20)

Scala有一个功能,您可以在其通用参数中定义一个协变/逆变的类。

作为协方差的一个例子:很自然地认为如果class Student extends Person然后List[Student]“延伸”List[Person]。这是因为接受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的键类型是不变的。原因是Map[A, B]就像一个函数A => B,所以它只能在A中逆变。另一种方法是考虑如果将Map[Student, String]传递给期望Map[Person, String]的方法会发生什么,显然它可能会尝试将Person个对象放在那里,这是不好的,另一种方式是可以的。另一方面,Map可以被视为Iterable[(A, B)],这里它应该是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]()
                       ^

答案 1 :(得分:4)

只需给编译器一些帮助:

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

我会详细说明为什么你的解决方案不适合那些更熟悉Scala类型推理的人...

修改:有关此行为的详细解释,请参阅IttayD's answer