如何编写一个zipWith方法,返回与传递给它的集合相同类型的集合?

时间:2010-10-09 05:14:52

标签: scala scala-2.8 scala-collections

我到目前为止:

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)谢谢。

3 个答案:

答案 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提供了关于类型构造函数模式的一些背景知识。