使用scala.math.max减少Float数组

Aus*_*tin 4 scala scala-2.10

我对以下行为感到困惑 - 为什么使用math.max减少一个Int数组,但是一个Float数组需要一个包装函数?我记得这不是2.9的问题,但我不能完全确定.

$ scala -version
Scala code runner version 2.10.2 -- Copyright 2002-2013, LAMP/EPFL

$ scala

scala> import scala.math._

scala> Array(1, 2, 4).reduce(max)
res47: Int = 4

scala> Array(1f, 3f, 4f).reduce(max)
<console>:12: error: type mismatch;
 found   : (Int, Int) => Int
 required: (AnyVal, AnyVal) => AnyVal
          Array(1f, 3f, 4f).reduce(max)
                                   ^

scala> def fmax(a: Float, b: Float) = max(a, b)
fmax: (a: Float, b: Float)Float

scala> Array(1f, 3f, 4f).reduce(fmax)
res45: Float = 4.0
Run Code Online (Sandbox Code Playgroud)

更新:这确实有效

scala> Array(1f, 2f, 3f).reduce{(x,y) => math.max(x,y)}
res2: Float = 3.0
Run Code Online (Sandbox Code Playgroud)

那么它是reduce(math.max)不是缺少什么?

Rég*_*les 6

首先要注意的math.max是重载,如果编译器没有关于预期参数类型的提示,它只选择其中一个重载(我还不清楚哪些规则控制选择哪个重载,但它会变成在这篇文章结束前明确).

显然它有利于过载,其中Int 参数超过其他参数.这可以在repl中看到:

scala> math.max _
res6: (Int, Int) => Int = <function2>
Run Code Online (Sandbox Code Playgroud)

该方法最具体,因为以下第一个编译(通过数字扩展转换),第二个不编译:

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)
                                         ^
Run Code Online (Sandbox Code Playgroud)

测试是一个函数是否适用于另一个函数的参数类型,该测试包括任何转换.

现在,问题是:为什么编译器不能推断出正确的期望类型?它当然知道的类型Array(1f, 3f, 4f)Array[Float]

我们可以得到一个线索,如果我们替换reducereduceLeft:那么它编译罚款.

所以这肯定有在签名的差异做reduceLeftreduce.我们可以使用以下代码片段重现错误:

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
Run Code Online (Sandbox Code Playgroud)

签名略有不同.

reduceLeft第二个参数被强制A(集合的类型),因此类型推断是微不足道的:如果A == Float(编译器知道),那么编译器知道唯一有效的重载max是一个Float作为其第二个参数的重载.编译器只找到一个(max(Float,Float)),并且发生另一个约束(那个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
Run Code Online (Sandbox Code Playgroud)

尝试对类型参数的每个可能替换进行重载解析是昂贵的,并且可能会根据您预期的类型更改结果; 或者它是否必须发出歧义错误?

使用-Xlog-implicits -Yinfer-debug显示reduce(math.max)首先发生重载解析的区别与首先解决param类型的版本之间的差异:

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
}
Run Code Online (Sandbox Code Playgroud)