首先要注意的是它math.max
是重载的,如果编译器没有关于预期参数类型的提示,它只会选择一个重载(我还不清楚哪些规则控制选择哪个重载,但它会变成在这篇文章结束之前清除)。
显然,它有利于采用Int
参数而不是其他参数的重载。这可以在repl中看到:
scala> math.max _
res6: (Int, Int) => Int = <function2>
该方法是最具体的,因为以下第一个编译(通过数值扩展转换)而第二个不编译:
scala> (math.max: (Float,Float)=>Float)(1,2)
res0: Float = 2.0
scala> (math.max: (Int,Int)=>Int)(1f,2f)
<console>:8: error: type mismatch;
found : Float(1.0)
required: Int
(math.max: (Int,Int)=>Int)(1f,2f)
^
测试是一个函数是否适用于另一个函数的参数类型,并且该测试包括任何转换。
现在,问题是:为什么编译器不能推断出正确的预期类型?它当然知道类型Array(1f, 3f, 4f)
是Array[Float]
如果我们reduce
用reduceLeft
: 替换我们可以得到一个线索,那么它编译得很好。
所以这肯定与 和 的签名不同reduceLeft
有关reduce
。我们可以使用以下代码片段重现错误:
case class MyCollection[A]() {
def reduce[B >: A](op: (B, B) => B): B = ???
def reduceLeft[B >: A](op: (B, A) => B): B = ???
}
MyCollection[Float]().reduce(max) // Fails to compile
MyCollection[Float]().reduceLeft(max) // Compiles fine
签名略有不同。
在reduceLeft
第二个参数中强制为A
(集合的类型),所以类型推断是微不足道的:如果 A==Float(编译器知道),那么编译器知道唯一有效的重载max
是一个将 aFloat
作为其第二个参数的重载. 编译器只找到一个 ( max(Float,Float)
),并且恰好满足另一个约束 (that B >: A
)(B == A == Float
对于这个重载)。
这是不同的reduce
:第一个和第二个参数都可以是任何(相同的)超类型A
(即Float
在我们的特定情况下)。这是一个更加宽松的约束,虽然可以说在这种情况下编译器可以看到只有一种可能性,但编译器在这里不够聪明。编译器是否应该能够处理这种情况(这意味着这是一个推理错误),我必须说我不知道。类型推断在 scala 中是一项棘手的工作,据我所知,规范故意模糊了可以推断的内容。
由于有一些有用的应用程序,例如:
scala> Array(1f,2f,3f).reduce[Any](_.toString+","+_.toString)
res3: Any = 1.0,2.0,3.0
尝试对类型参数的每个可能替换进行重载解析是昂贵的,并且可能会根据您最终得到的预期类型改变结果;还是必须发出模棱两可的错误?
Using显示了首先发生重载解析的版本与首先解决参数类型的版本-Xlog-implicits -Yinfer-debug
之间的区别:reduce(math.max)
scala> Array(1f,2f,3f).reduce(math.max(_,_))
[solve types] solving for A1 in ?A1
inferExprInstance {
tree scala.this.Predef.floatArrayOps(scala.Array.apply(1.0, 2.0, 3.0)).reduce[A1]
tree.tpe (op: (A1, A1) => A1)A1
tparams type A1
pt ?
targs Float
tvars =?Float
}