11

我已经到了这一步:

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

4

3 回答 3

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]作为类型参数。此外,该集合需要能够“复制”自身——这是由 --so 的第二个类型参数保证IterableLikeCC[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)
于 2010-10-09T15:17:00.810 回答
8

上面的问题是你的隐式转换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)
于 2010-10-09T15:19:06.063 回答
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)

我有点猴子从retronym修补了这个答案,直到它起作用!

基本上,我想使用CC[X]类型构造函数来指示zipWith应该返回集合类型,xs但是C作为类型参数(CC[C])。我想用它breakOut来获得正确的结果类型。我有点希望有一个CanBuildFrom隐含的范围,但后来得到了这个错误信息:

required: scala.collection.generic.CanBuildFrom[Iterable[(A, B)],C,CC[C]]

诀窍是使用Nothing而不是Iterable[(A, B)]. 我猜隐式是在某处定义的......

另外,我喜欢把你的zipWithas zipand then想成是map,所以我改变了实现。这是您的实现:

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] = {
    val builder = cbf()
    val (i, j) = (xs.iterator, ys.iterator)
    while(i.hasNext && j.hasNext) {
      builder += f(i.next, j.next)
    }
    builder.result
  }
}

请注意,本文提供了有关类型构造函数模式的一些背景知识。

于 2010-10-09T07:43:19.797 回答