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

Jim*_*Jim 11 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)
Run Code Online (Sandbox Code Playgroud)

现在问题是上面的方法总是返回一个Iterable.如何让它返回传递给它的类型集合?(在这种情况下,Vector)谢谢.

Dan*_*ral 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
  }
}
Run Code Online (Sandbox Code Playgroud)

首先,您需要传递集合类型,因此我添加CC[A]了一个类型参数.此外,该集合需要能够"重现"自身 - 这是由第二类参数保证的IterableLike- 所以CC[A] <: IterableLike[A, CC[A]].请注意,这第二个参数IterableLikeRepr,恰恰类型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)
Run Code Online (Sandbox Code Playgroud)


axe*_*l22 8

上面的问题是您的隐式转换collectionExtras导致获取的对象丢失类型信息.特别是,在上面的解决方案中,具体的集合类型丢失了,因为你传递了一个类型的对象Iterable[A]- 从这一点开始,编译器不再知道真正的类型xs.尽管构建器工厂以CanBuildFrom编程方式确保集合的动态类型是正确的(您真的得到了Vector),但静态地,编译器只知道zipWith返回的内容是一个Iterable.

要解决这个问题,不要Iterable[A]让隐式转换占用,而是让它采取IterableLike[A, Repr].为什么?

Iterable[A] 通常被声明为:

Iterable[A] extends IterableLike[A, Iterable[A]]
Run Code Online (Sandbox Code Playgroud)

与之不同的Iterable是,它将IterableLike[A, Repr]具体的集合类型保持为Repr.大多数具体的收藏品,除了混合之外Iterable[A],还混合了特性IterableLike[A, Repr],用Repr它们的混凝土类型代替,如下所示:

Vector[A] extends Iterable[A] with IterableLike[A, Vector[A]]
Run Code Online (Sandbox Code Playgroud)

他们可以这样做,因为type参数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
  }
}
Run Code Online (Sandbox Code Playgroud)

更仔细地阅读你的问题公式("如何编写一个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
  }
}
Run Code Online (Sandbox Code Playgroud)

结果如下:

scala> immutable.Vector(2, 2, 2).zipWith(mutable.ArrayBuffer(4, 4, 4))(_ * _)
res1: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(8, 8, 8)
Run Code Online (Sandbox Code Playgroud)


huy*_*hjl 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)
Run Code Online (Sandbox Code Playgroud)

我有点猴子从反语中修补这个答案,直到它奏效!

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

required: scala.collection.generic.CanBuildFrom[Iterable[(A, B)],C,CC[C]]
Run Code Online (Sandbox Code Playgroud)

然后用诀窍Nothing代替Iterable[(A, B)].我猜隐含在某个地方被定义了......

另外,我喜欢想你的zipWith作为zip,然后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
  }
}
Run Code Online (Sandbox Code Playgroud)

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