使用Map#getOrElse键入怪异

时间:2014-09-17 18:26:27

标签: scala

考虑

scala> val m = Map('a -> 3, 'b -> 4)
m: scala.collection.immutable.Map[Symbol,Int] = Map('a -> 3, 'b -> 4)

scala> val d: Double = m.getOrElse('c, 0)
<console>:8: error: type mismatch;
 found   : AnyVal
 required: Double
       val d: Double = m.getOrElse('c, 0)
                                  ^

scala> m.getOrElse('c, 0)
res0: Int = 0

scala> m.getOrElse('a, 0)
res1: Int = 3

为什么Scala认为getOrElse调用会返回AnyVal,即使它显然会返回Int

此外,即使失败也会出现同样的错误:

scala> val x: Double = m.getOrElse('a, 0): Double
<console>:8: error: type mismatch;
 found   : AnyVal
 required: Double
       val x: Double = m.getOrElse('a, 0): Double

然而这有效:

scala> val x: Double = m.getOrElse('a, 0): Int
x: Double = 3.0

这发生在2.11.x上;我没有在2.10.x上尝试过它。

3 个答案:

答案 0 :(得分:8)

getOrElse的签名是:

def getOrElse[B1 >: B](key: A, default: ⇒ B1): B1 

发表声明

val d: Double = m.getOrElse('c, 0)

您告诉Scala B1应该是Double。但Double不是Int的超类型,而常见的超类型是AnyVal。因此,你不能做那个任务......

尝试将Int结果转换为Double

val d /* : Double */ = m.getOrElse('c, 0).toDouble

答案 1 :(得分:6)

因为map在其第二个类型参数中是协变的,所以我们需要使用特殊技巧来避免逆变问题,即指定此类型的上限:

def getOrElse[B1 >: B](key: A, default: => B1): B1

因为您明确规定返回类型为Double,scalac会将其常用超类型解析为AnyVal,因为IntDoubleAnyVal的子类型。在第二种情况下,scalac会看到类型归属,因此它会正确推断B1,然后将Int转换为Double。

答案 2 :(得分:5)

我正在努力改善阅读-Ytyper-debug输出。

使用预期类型Double0的类型将被视为Double0.0。那是因为人们讨厌的转换次数越来越多。

这是在解决B1之前。 B1被视为AnyVal,因为这是A / IntDouble的润滑。

|    |-- Double TYPEmode (site: value d  in $iw) 
|    |    \-> Double
|    |-- m.getOrElse(scala.Symbol("c"), 0) : pt=Double BYVALmode-EXPRmode (site: value d  in $iw) 
|    |    |-- m.getOrElse BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value d  in $iw) 
|    |    |    |-- m EXPRmode-POLYmode-QUALmode (silent: value d  in $iw) 
|    |    |    |    \-> m.type (with underlying type scala.collection.immutable.Map[Symbol,Int])
|    |    |    [adapt] [B1 >: B](key: A, default: => B1)B1 adapted to [B1 >: B](key: A, default: => B1)B1
|    |    |    \-> (key: Symbol, default: => B1)B1
|    |    |-- scala.Symbol("c") : pt=Symbol BYVALmode-EXPRmode-POLYmode (site: value d  in $iw) 
|    |    |    |-- scala.Symbol BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value d  in $iw) 
|    |    |    |    |-- scala.Symbol.apply BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value d  in $iw) 
|    |    |    |    |    \-> (name: String)Symbol
|    |    |    |    [adapt] Symbol.type adapted to (name: String)Symbol
|    |    |    |    \-> (name: String)Symbol
|    |    |    |-- "c" : pt=String BYVALmode-EXPRmode (silent: value d  in $iw) 
|    |    |    |    \-> String("c")
|    |    |    \-> Symbol
|    |    |-- 0 : pt=Double EXPRmode-POLYmode (site: value d  in $iw) 
|    |    |    \-> Double(0.0)
|    |    solving for (B1: ?B1) 
<console>:8: error: type mismatch;
 found   : AnyVal
 required: Double
       val d: Double = m.getOrElse('c, 0)
                                  ^
|    |    \-> <error>

此外,-xprint:typer显示

m.getOrElse[AnyVal](scala.Symbol.apply("c"), 0.0);

使用预期的param类型对args进行类型检查的spec explains(案例3方法),这就是为什么你在那里转换到Double,而不是让方法返回{ {1}}在作业中扩展为Int