4

我对以下行为感到困惑 - 为什么使用 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)不能被人手短缺的呢?

4

4 回答 4

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]

如果我们reducereduceLeft: 替换我们可以得到一个线索,那么它编译得很好。

所以这肯定与 和 的签名不同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
}
于 2013-08-07T17:22:31.037 回答
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))
});

但有浮动:

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))
});
于 2013-08-07T17:04:26.867 回答
2

总是有 scala.math.Ordering:

Array(1f, 2f, 3f).reduceOption(Ordering.Float.max)
于 2013-08-07T17:33:10.280 回答
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有一个到 Scala 的隐式转换Float试图找到最具体的类型min,即(Int, Int) => Int. 和 最接近的共同超类IntFloatAnyVal这就是你看到的原因(AnyVal, AnyVal) => AnyVal

起作用的原因(x, y) => min(x, y)可能是因为 eta-expansion 是类型推断之前完成的,并且reduce必须处理(Int, Int) => Int将转换为(AnyVal, AnyVal) => AnyVal.

更新:同时(new C3).f(_)会因“缺少参数类型”错误而失败,这意味着f(_)依赖于类型推断并且不考虑隐式转换,同时f _不需要参数类型,如果 Scala 可以找到一个,它将扩展到最具体的参数类型。

于 2013-08-07T18:36:40.057 回答