Проблема выше в том, что ваше неявное преобразование 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)