我对以下行为感到困惑 - 为什么使用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
更新:这确实有效
scala> Array(1f, 2f, 3f).reduce{(x,y) => math.max(x,y)}
res2: Float = 3.0
那么它只是reduce(math.max)
不能缩短?
答案 0 :(得分:6)
首先要注意的是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
的有效重载是将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
针对类型参数的每个可能替换尝试重载解析是昂贵的,并且可能会根据您预期的类型更改结果;或者它是否必须发出歧义错误?
使用-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
}
答案 1 :(得分:2)
看起来这是推理器中的一个错误,导致Int
它正确地推断出类型:
private[this] val res2: Int = scala.this.Predef.intArrayOps(scala.Array.apply(1, 2, 4)).reduce[Int]({
((x: Int, y: Int) => scala.math.`package`.max(x, y))
});
但是使用Floats:
private[this] val res1: AnyVal = scala.this.Predef.floatArrayOps(scala.Array.apply(1.0, 3.0, 4.0)).reduce[AnyVal]({
((x: Int, y: Int) => scala.math.`package`.max(x, y))
});
如果使用Float类型明确注释reduce,它应该可以工作:
Array(1f, 3f, 4f).reduce[Float](max)
private[this] val res3: Float = scala.this.Predef.floatArrayOps(scala.Array.apply(1.0, 3.0, 4.0)).reduce[Float]({
((x: Float, y: Float) => scala.math.`package`.max(x, y))
});
答案 2 :(得分:2)
总是有scala.math.Ordering:
Array(1f, 2f, 3f).reduceOption(Ordering.Float.max)
答案 3 :(得分:0)
它似乎不是一个错误。请考虑以下代码:
class C1 {}
object C1 {
implicit def c2toc1(x: C2): C1 = new C1
}
class C2 {}
class C3 {
def f(x: C1): Int = 1
def f(x: C2): Int = 2
}
(new C3).f _ //> ... .C2 => Int = <function1>
如果我删除隐式转换,我会收到错误的“模糊引用”。因为Int
隐式转换为Float
,Scala会尝试找到min
的最具体类型,即(Int, Int) => Int
。 Int
和Float
最接近的公共超类是AnyVal
,这就是您看到(AnyVal, AnyVal) => AnyVal
的原因。
(x, y) => min(x, y)
之所以有效的原因可能是因为在类型推断之前完成了,而reduce
必须处理将转换为(Int, Int) => Int
的{{1}} (AnyVal, AnyVal) => AnyVal
。
更新:同时(new C3).f(_)
将失败并显示“缺少参数类型”错误,这意味着f(_)
取决于类型推断,并且在{{1}时不考虑隐式转换不需要参数类型,如果Scala可以找到一个参数类型,它将扩展为最具体的参数类型。