オーバーロードされたバージョンのapply
を持つ次のコンパニオンオブジェクトがあるとします。
_object List {
def apply[T](): List[T] = new Nil
def apply[T](x1: T): List[T] = new Cons(x1, new Nil)
def apply[T](x1: T, x2: T): List[T] = new Cons(x1, new Cons(x2, new Nil))
def apply[T](elems: T*): List[T] =
elems.foldRight(List[T])((elem, l) => new Cons(elem, l))
}
_
そして2つのインスタンス化
_List(1) // Error - Ambiguity
List('a', 'b') // Works fine
_
scalacは、最初のインスタンス化(オーバーロードされた定義へのあいまいな参照)について文句を言います。これは、単一の引数とvarargsメソッドの両方が 等しく特定 。
Stackoverflowを検索すると、 単一引数メソッドを強制する が可能であることがわかりました。 List[Int](1)
はコンパイラにdef apply[T](x1: T)
を使用させます。
私の質問は、2番目のインスタンス化が余分な「ヒント」なしでdef apply[T](x1: T, x2: T)
と一致するのはなぜですか?言い換えると、2つの引数のメソッドが1つの引数のメソッドではないvarargsメソッドよりも特定のであるのはなぜですか?
あなたの質問に答えるには、Scalaコンパイラがオーバーロード解決を実行しなければならないときに何が起こるかを調べる必要があります。これはSLS6.23.3(Scala 2.9の場合)で説明されています。
少し単純なバージョンの例を見てみましょう。
_object Test {
def apply[T](x1: T) = "one arg" // A
def apply[T](x1: T, x2: T) = "two args" // B
def apply[T](elems: T*) = "var-args: " + elems.size // C
}
_
そして、これらの3つの呼び出しを見てください。
_Test(1) // fails, ambiguous reference, A and C both match arguments
Test[Int](1) // returns "one arg"
Test(1,2) // returns "two args", not "var-args: 2"
_
最初のものから始めましょう。まず、コンパイラは各引数のshapeを調べます。これは、基本的に、引数が値であるか関数であるかを記述する型です。ここでは、問題はありません。_1
_は非常に普通の退屈な値であり、その形状はタイプNothing
です。
これで、タイプNothing
の単一の引数_1
_があり、それに適用できるすべての選択肢が見つかります。それらのうちの2つを見つけます:
apply[T](x1: T)
:無制限タイプの単一の引数を取るので、タイプNothing
の引数を受け取ることができます。apply[T](elems: T*)
:同じ無制限タイプの任意の数(_0
_を含む)の引数に適用できるため、タイプNothing
の単一の要素を受け取ることができます。1つしかない場合は、そこで停止してその1つを選択します。
2番目のステップは上記と同じですが、今回は未定義の予期される型で各引数を型指定します。基本的にここでは、残っている2つの選択肢を調べて、タイプ_1
_の引数_A <: Int
_に適用できるかどうかを調べます。運が悪い、彼らは両方ともそうです。 2人でapply[T](x1: T)
をapply(x1: String)
に変更し、もう1つをそのままにしておくと、適用可能な選択肢は1つだけ残り、成功して停止します。
次に、コンパイラーは、互いに残っている各選択肢の_relative weight
_を計算します。 SLSは次のように述べています
代替Bに対する代替Aの相対重みは、0から2までの数値であり、
- AがBと同じくらい具体的である場合は1、それ以外の場合は0、
- AがBを定義するクラスまたはオブジェクトから派生したクラスまたはオブジェクトで定義されている場合は1、それ以外の場合は0。
この時点で、他のすべてよりも高いスコアを持つ1つの選択肢が存在する必要があります。そうでない場合、あいまいさのエラーが発生します。 「定義された」部分は無視できます。同じ場所で定義されています。
A
はC
と同じくらい具体的です。これは、C
の単一の引数でいつでもA
を呼び出すことができるためです。C
は型推論のため、A
と同じくらい具体的です。A
は取ることができるので、C
の引数を使用していつでもA
を呼び出すことができます。何でも(そのパラメーターのタイプは、必要なものに推測できます)。 C
のパラメータは_Seq[A]
_と見なされるため、T
はA
では_Seq[A]
_として推測され、呼び出すことができます。したがって、C
はA
と同じくらい具体的です。これは、A
をapply[T <: Int](x: T)
に変更するとわかります。最も具体的なものを探すのに役立ちますが、今回の型推論ではA
はC
のサブタイプではないため、Seq
の引数(Int
)に適用できます。したがって、A
が最も具体的です。明らかに、A
をapply(x: Int)
に変更した場合も同じことが起こり、型推論は何もできません。
これは、Test[Int](1)
が1つの引数バージョンの呼び出しに成功する理由も説明しています。最後の2つの選択肢は同じですが、A
の型パラメーターはInt
にバインドされており、型推論ではC
の引数に合うように変更できなくなりました。
最後に、同じロジックを適用すると、Test(1,2)
が正常に機能する理由がわかります。
B
はC
と同じくらい具体的です。C
の引数を使用していつでもB
を呼び出すことができます。C
はnotB
と同じくらい具体的です:型推論の量は、単一のSeq
を2つのパラメータ。したがって、apply[T](x1: T, x2: T)
が最も具体的であり、エラーはありません。
基本的に、var-argと通常のメソッドであいまいさを生成するには、同じ数の引数と、(少なくとも)最後の引数で型推論をだます方法が必要です。
_def apply[T](x1: T)
def apply[T](x: T*)
_
または
_def apply[T](x1: Int, x2:T)
def apply[T](x1: Int, x: T*)
_
等々...
編集:特異性を探すときに、繰り返されるパラメーターが_Seq[A]
_と表示されるのか_TupleX[...]
_と表示されるのか最初はわかりませんでした。それは間違いなくタプルではなく、自動タプルはこれとは何の関係もありません。
固定アリティ法は、常に変数よりも具体的です。
f(P1, P2)
は_(a, b, c, ...)
_には適用されません。これは、f(P*)
の考え方です。
ただし、逆に、f(P*)
はN個の引数に適用できるようにf(p1,..,pn)
の形を取ります。したがって、それは常に適用され、固定アリティ法ほど具体的ではありません。
これが、f(a,b)
がf(P*)
よりも具体的である通常の理由です。
1引数の場合、タイプparamに何を選択するかによって異なります。
f[A](a: A)
は、Aをタプルとしてタプルし、取得することにより、_(a, b, ...)
_に適用されます。
A = Intと言うと、明らかにAをタプルと見なすことはできません。
多様性と特異性にどのように影響するかについてのサンプルの混乱:
https://issues.scala-lang.org/browse/SI-4728
_object Foo {
def apply[T](): Int = 1
def apply[T](x1: T): Int = 2
def apply[T](x1: T, x2: T): Int = 3
def apply[T](elems: T*): Int = 4
// two or more
def canonically[T](x1: T, x2: T, rest: T*): List[T] = ???
}
object Test extends App {
Console println Foo(7)
Console println Foo[Int](7)
Console println Foo(7,8)
Console println Foo[Pair[Int,Int]](7,8)
}
_
この質問は、過負荷のスペシャリストが集まるサイト、stackoverload.comに投稿することをお勧めします。お使いのブラウザはoverloading-is-evil.comにリダイレクトされる場合があります。