4

次の動作に混乱しています - int 配列の削減は math.max を使用して機能するのに、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は、オーバーロードされていることです。コンパイラが予想される引数の型に関するヒントを持っていない場合、オーバーロードの 1 つを選択するだけです (どのオーバーロードが選択されるかを制御する規則についてはまだ明確ではありませんが、次のようになります。この記事が終わる前にクリアしてください)。

どうやらInt 、他のパラメーターよりもパラメーターを取るオーバーロードが優先されます。これはreplで見ることができます:

scala> math.max _
res6: (Int, Int) => Int = <function2>

次の最初のメソッドは (数値の拡大変換によって) コンパイルされ、2 番目のメソッドはコンパイルされないため、このメソッドが最も具体的です。

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

サインが微妙に違う。

2reduceLeft番目の引数では (コレクションの型) が強制されるAため、型の推論は自明です: A==Float (コンパイラが認識する) の場合、コンパイラは、の唯一の有効なオーバーロードが2 番目の引数としてmaxa を取るものであることを認識します。 Float. コンパイラは 1 つの ( ) しか見つけず、もう 1 つmax(Float,Float)の制約 ( B >: A) は自明に満たされます (B == A == Floatこのオーバーロードに関して)。

の場合は異なりreduceます。最初の引数と 2 番目の引数の両方が、任意の (同じ) スーパータイプのA(つまりFloat、この特定のケースではの) である可能性があります。これははるかに緩い制約であり、この場合、コンパイラーは可能性が 1 つしかないことを認識できると主張できますが、コンパイラーはここで十分にスマートではありません。コンパイラがこのケース (つまり、これは推論のバグ) を処理できるはずなのかどうか、私にはわからないと言わざるを得ません。型推論は scala ではトリッキーなビジネスであり、私が知る限り、仕様は推論できるものとできないものについて意図的にあいまいです。

次のような便利なアプリケーションがあるため:

scala> Array(1f,2f,3f).reduce[Any](_.toString+","+_.toString)
res3: Any = 1.0,2.0,3.0

型パラメーターのすべての可能な置換に対してオーバーロードの解決を試みるのは高価であり、予想される型に応じて結果が変わる可能性があります。または、あいまいなエラーを発行する必要がありますか?

を使用すると、オーバーロードの解決が最初に行われる と、最初に param 型が解決されるバージョン-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))
});

reduce に Float 型で明示的に注釈を付けると、動作するはずです。

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 に答える
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>

暗黙的な変換を削除すると、「あいまいな参照」というエラーが発生します。そして、 ScalaIntへの暗黙的な変換はFloat、 の最も具体的な型、つまり を見つけようとするためminです(Int, Int) => Int。との最も近い共通のスーパークラスは です。そのため、 が表示さIntFloatます。AnyVal(AnyVal, AnyVal) => AnyVal

動作する理由(x, y) => min(x, y)は、おそらく eta-expansion が型推論の前に行われ、どれが に変換されるかreduceを処理する必要があるためです。(Int, Int) => Int(AnyVal, AnyVal) => AnyVal

UPDATE : 一方(new C3).f(_)、「missing parameter type」エラーで失敗します。これはf(_)、型推論に依存し、暗黙的な変換を考慮しない一方f _で、パラメーターの型を必要とせず、Scala が見つけられる場合は最も具体的な引数の型に展開することを意味します。

于 2013-08-07T18:36:40.057 に答える