上面的问题是你的隐式转换collectionExtras
导致获取的对象丢失了类型信息。特别是,在上面的解决方案中,具体的集合类型丢失了,因为您向它传递了一个类型的对象Iterable[A]
——从这一点开始,编译器不再知道xs
. 尽管构建器工厂以CanBuildFrom
编程方式确保集合的动态类型是正确的(您确实得到了 a Vector
),但静态地,编译器只知道zipWith
返回的东西是 a Iterable
。
为了解决这个问题,与其让隐式转换取一个Iterable[A]
,不如让它取一个IterableLike[A, Repr]
。为什么?
Iterable[A]
通常被声明为:
Iterable[A] extends IterableLike[A, Iterable[A]]
不同之Iterable
处在于,这IterableLike[A, Repr]
将具体集合类型保持为Repr
. 大多数具体集合,除了混入Iterable[A]
,还混入 trait IterableLike[A, Repr]
,用Repr
它们的具体类型替换 ,如下所示:
Vector[A] extends Iterable[A] with IterableLike[A, Vector[A]]
他们可以这样做,因为类型参数Repr
被声明为协变。
长话短说, usingIterableLike
导致您隐式转换以保留具体的集合类型信息(即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)