【问题标题】:Problem with bounded type parameterised case class and default args in ScalaScala中的有界类型参数化案例类和默认参数的问题
【发布时间】:2011-09-02 09:38:09
【问题描述】:

考虑以下(使用 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]?有没有办法创建一个使用 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

【问题讨论】:

    标签: scala case-class default-arguments


    【解决方案1】:

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

    作为协方差的一个例子:很自然地认为如果class Student extends Person 那么List[Student]“扩展”List[Person]。这是因为每个接受List[Person] 的方法在使用对象List[Student] 时应该没有问题。这在 Java 中是不可能的(不使该方法也通用)。

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

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

    无论如何,Map 的键类型是不变的。原因是Map[A, B] 就像一个函数A =&gt; 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]()
                           ^
    

    【讨论】:

    • 啊,对,关于List() 返回List[Nothing] 的好处。 @Kristian,然后你会遇到与 Set 相同的问题,因为集合是(不幸的是……)invariant in their element type (yes, immutable sets, too)
    • 嗯,实际上 Map 在它的 key 类型上是不变的,而不是它的 value 类型(它是协变的)。但是,是的,编译器将键类型推断为 Nothing 的事实似乎是导致问题的原因。我想这是编译器中使用的推理器的限制,它不能被推断为字符串。我接受了@Jean-Philippe 的回答,因为它回答了问题的第二部分,目前这对我来说更重要:-)
    【解决方案2】:

    给编译器一点帮助:

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

    我会将您的解决方案为何不起作用的详细信息留给更熟悉 Scala 类型推断的人...

    编辑:请参阅 IttayD's answer 以了解有关此行为的详细说明。

    【讨论】:

    • 谢谢,这是我正在寻找的编译器提示,以避免在调用站点出现类型注释。
    • 或 Map.empty[String, A] 以避免创建新的空地图。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多