上記の問題は、暗黙の変換collectionExtras
により、取得したオブジェクトが型情報を失うことです。特に、上記のソリューションでは、具体的なコレクションタイプは、タイプのオブジェクトを渡すために失われます。Iterable[A]
この時点から、コンパイラは実際のタイプを認識しなくなりxs
ます。ビルダーファクトリCanBuildFrom
は、コレクションの動的タイプが正しいことをプログラムで保証しますが(実際には、を取得しますVector
)、静的では、コンパイラzipWith
は、であるものを返すものだけを認識しますIterable
。
この問題を解決するには、暗黙の変換にをとらせる代わりに、をIterable[A]
とらせIterableLike[A, Repr]
ます。なんで?
Iterable[A]
通常、次のように宣言されます。
Iterable[A] extends IterableLike[A, Iterable[A]]
との違いIterable
は、これIterableLike[A, Repr]
により具象コレクションタイプがとして保持されることRepr
です。ほとんどのコンクリートコレクションは、混合に加えてIterable[A]
、特性も混合し、以下のように、をコンクリートタイプにIterableLike[A, Repr]
置き換えます。Repr
Vector[A] extends Iterable[A] with IterableLike[A, Vector[A]]
タイプパラメータRepr
が共変として宣言されているため、これを行うことができます。
簡単に言うと、を使用するIterableLike
と、暗黙的な変換によって具体的なコレクションタイプ情報(つまりRepr
)が保持され、定義時に使用されますzipWith
。ビルダーファクトリには、最初のタイプパラメータの代わりにCanBuildFrom
含まれるようになり、適切な暗黙のオブジェクトが解決される:Repr
Iterable[A]
import collection._
import collection.generic._
implicit def collectionExtras[A, Repr](xs: IterableLike[A, Repr]) = new {
def zipWith[B, C, That](ys: Iterable[B])(f: (A, B) => C)(implicit cbf: CanBuildFrom[Repr, C, That]) = {
val builder = cbf(xs.repr)
val (i, j) = (xs.iterator, ys.iterator)
while(i.hasNext && j.hasNext) {
builder += f(i.next, j.next)
}
builder.result
}
}
zipWith
質問の定式化をより注意深く読むと(「渡されたものと同じタイプのコレクションを返すzipWithメソッドを作成する方法は?」)、渡されたものと同じタイプのコレクションが必要なようです。暗黙の変換に、それはと同じタイプys
です。
以前と同じ理由で、以下の解決策を参照してください。
import collection._
import collection.generic._
implicit def collectionExtras[A](xs: Iterable[A]) = new {
def zipWith[B, C, That, Repr](ys: IterableLike[B, Repr])(f: (A, B) => C)(implicit cbf: CanBuildFrom[Repr, C, That]) = {
val builder = cbf(ys.repr)
val (i, j) = (xs.iterator, ys.iterator)
while(i.hasNext && j.hasNext) {
builder += f(i.next, j.next)
}
builder.result
}
}
結果:
scala> immutable.Vector(2, 2, 2).zipWith(mutable.ArrayBuffer(4, 4, 4))(_ * _)
res1: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(8, 8, 8)