【问题标题】:reducing an Array of Float using scala.math.max使用 scala.math.max 减少浮点数组
【发布时间】:2013-08-07 16:39:44
【问题描述】:

我对以下行为感到困惑 - 为什么使用 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)不能简写?

【问题讨论】:

  • 不知何故reduce的类型参数被推断为AnyVal。作为一种解决方法,您可以明确声明:Array(1f, 2f, 4f).reduce[Float](math.max)
  • 这是由方法重载引起的(再次......)。如果您添加另一个fmax 但使用Ints,那么您的reduce(fmax) 也会停止工作。
  • 哇,刚刚注意到虽然Array(1f, 2f, 3f).reduce(max) 无法编译,但以下工作:Array(1f, 2f, 3f).reduce(max(_,_))。我什至没有添加任何额外的类型注释。叫我大吃一惊。
  • 那是因为Array(1f, 2f, 3f).reduce((x,y) =&gt; max(x,y)) 有效。我能看到的唯一区别是重载解析在应用时的执行方式不同(max(x,y)),而不是作为函数值(eta-expansion + 解析为reduce 的参数的类型)。可悲的是,后者在规范中没有很好地描述......
  • 噢!我什至没有注意到 OP 的更新显示 reduce{(x,y) =&gt; math.max(x,y)} 有效。这种类型推断行为的差异真的很不幸。

标签: scala scala-2.10


【解决方案1】:

首先要注意的是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,我们可以得到一个线索:那么它编译得很好。

所以这肯定与reduceLeftreduce 的签名不同有关。 我们可以用下面的代码sn-p重现错误:

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 &gt;: 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)(首先发生重载解析)与首先解决参数类型的版本之间的区别:

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
}

【讨论】:

    【解决方案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))
    });
    

    【讨论】:

    • 你到底为什么要使用 AnyVal 显式类型注释?
    • @RichardSitze 没有看到他的评论,我在玩 REPL
    • 你确定这是一个错误?规范没有在任何地方提到它吗?
    • 这没有回答问题,即“为什么”。
    【解决方案3】:

    总是有scala.math.Ordering:

    Array(1f, 2f, 3f).reduceOption(Ordering.Float.max)
    

    【讨论】:

      【解决方案4】:

      这似乎不是一个错误。考虑以下代码:

      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) =&gt; IntIntFloat 最接近的公共超类是 AnyVal,这就是你看到 (AnyVal, AnyVal) =&gt; AnyVal 的原因。

      (x, y) =&gt; min(x, y) 起作用的原因可能是因为 eta-expansion 在 类型推断之前完成,reduce 必须处理 (Int, Int) =&gt; Int 将被转换为 (AnyVal, AnyVal) =&gt; AnyVal。 p>

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

      【讨论】:

      • 哦,我还没有看到隐式转换在这一切中的作用。 f _ 怎么没有歧义因为 C2 可以转换成C1
      • 哦,我明白了,这就是 SLS§6.26.3 的作用。 f(x: C1) 适用于(感谢隐式)给f(x: C2) 的参数,但不是相反,因此不再有歧义,f(x: C2) 更具体(在SLS 感知)并被重载分辨率拾取。
      猜你喜欢
      • 1970-01-01
      • 2014-01-03
      • 2017-12-17
      • 1970-01-01
      • 1970-01-01
      • 2018-07-09
      • 2015-02-05
      • 2015-03-03
      • 1970-01-01
      相关资源
      最近更新 更多