我到目前为止:
implicit def collectionExtras[A](xs: Iterable[A]) = new {
def zipWith[B, C, That](ys: Iterable[B])(f: (A, B) => C)(implicit cbf: CanBuildFrom[Iterable[A], 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
}
}
// collectionExtras: [A](xs: Iterable[A])java.lang.Object{def zipWith[B,C,That](ys: Iterable[B])(f: (A, B) => C)(implicit cbf: scala.collection.generic.CanBuildFrom[Iterable[A],C,That]): That}
Vector(2, 2, 2).zipWith(Vector(4, 4, 4))(_ * _)
// res3: Iterable[Int] = Vector(8, 8, 8)
现在问题是上面的方法总是返回Iterable
。如何让它返回传递给它的类型集合? (在这种情况下,Vector
)谢谢。
答案 0 :(得分:9)
你足够近了。只是两行中的微小变化:
implicit def collectionExtras[A, CC[A] <: IterableLike[A, CC[A]]](xs: CC[A]) = new {
def zipWith[B, C, That](ys: Iterable[B])(f: (A, B) => C)(implicit cbf: CanBuildFrom[CC[A], 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
}
}
首先,您需要传递集合类型,因此我添加了CC[A]
作为类型参数。此外,该集合需要能够“重现”自身 - 这由IterableLike
的第二个类型参数保证 - 所以CC[A] <: IterableLike[A, CC[A]]
。请注意,IterableLike
的第二个参数是Repr
,正好是xs.repr
的类型。
当然,CanBuildFrom
需要接收CC[A]
而不是Iterable[A]
。这就是它的全部内容。
结果:
scala> Vector(2, 2, 2).zipWith(Vector(4, 4, 4))(_ * _)
res0: scala.collection.immutable.Vector[Int] = Vector(8, 8, 8)
答案 1 :(得分:8)
上面的问题是您的隐式转换collectionExtras
会导致获取的对象丢失类型信息。特别是,在上面的解决方案中,具体的集合类型丢失了,因为你传递了一个Iterable[A]
类型的对象 - 从这一点开始,编译器不再知道xs
的真实类型。虽然构建器工厂CanBuildFrom
以编程方式确保集合的动态类型是正确的(您真的得到Vector
),但静态地,编译器只知道zipWith
返回的内容是{ {1}}。
要解决此问题,请使用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]
更仔细地阅读你的问题公式(“如何编写一个zipWith方法,返回与传递给它的集合相同类型的集合?”),在我看来,你想要拥有与传递的集合相同类型的集合到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
的类型相同。
与以前相同的原因,请参阅以下解决方案:
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
}
}
答案 2 :(得分:5)
说实话,我不确定这是如何运作的:
implicit def collectionExtras[CC[X] <: Iterable[X], A](xs: CC[A]) = new {
import collection.generic.CanBuildFrom
def zipWith[B, C](ys: Iterable[B])(f: (A, B) => C)
(implicit cbf:CanBuildFrom[Nothing, C, CC[C]]): CC[C] = {
xs.zip(ys).map(f.tupled)(collection.breakOut)
}
}
scala> Vector(2, 2, 2).zipWith(Vector(4, 4, 4))(_ * _)
res1: scala.collection.immutable.Vector[Int] = Vector(8, 8, 8)
我修补了this answer from retronym的猴子,直到它奏效!
基本上,我想使用CC[X]
类型构造函数来指示zipWith
应该返回xs
的集合类型,但C
作为类型参数({{ 1}})。我想使用CC[C]
来获得正确的结果类型。我有点希望范围内隐含breakOut
,但后来收到了这条错误消息:
CanBuildFrom
然后使用required: scala.collection.generic.CanBuildFrom[Iterable[(A, B)],C,CC[C]]
代替Nothing
。我猜隐含是在某处定义的......
另外,我想将您的Iterable[(A, B)]
视为zipWith
然后zip
,因此我更改了实施方案。以下是您的实施:
map
注意this article提供了关于类型构造函数模式的一些背景知识。