【问题标题】:How to write a zipWith method that returns the same type of collection as those passed to it?如何编写一个 zipWith 方法来返回与传递给它的集合类型相同的集合?
【发布时间】:2011-04-23 04:30:54
【问题描述】:

我已经走到这一步了:

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

【问题讨论】:

  • new { def foo = } 产生一个结构类型的值,通过反射调用;为避免这种情况,请在特征 ZipWith 中声明签名,并返回此特征的实例。这适用于问题和所有解决方案。

标签: scala scala-2.8 scala-collections


【解决方案1】:

你已经足够接近了。只是两行的小改动:

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] &lt;: 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)

【讨论】:

  • 下拉到hasNext 这意味着它不适用于无限流。 huynhjl 的解决方案可以。
  • 我的回答有两个问题:a) 你写了CC[A],这在压缩列表时会出现问题(Scala 集合库不以这种方式使用更高级别的类型是有原因的,并且我相信你知道)。 b) 这个答案也使collectionExtras 返回类型成为结构类型(因此效率低下)。
  • @Blaisorblade 如果您查看我对 axel22 的评论,当时我没有想到其他解决方案。至于结构类型,我不明白它是如何产生的——结构类型可能会很棘手。 :-/
  • 关于结构类型,我看不出有什么棘手,所以我想我不明白你。返回类型是java.lang.Object{def zipWith...,即结构类型,当然是这种情况,因为您使用new {def ...,并且定义的方法(zipWith)不是Object的方法。完整类型为:collectionExtras: [A, CC[A] &lt;: scala.collection.IterableLike[A,CC[A]]](xs: CC[A])java.lang.Object{def zipWith[B,C,That](ys: Iterable[B])(f: (A, B) =&gt; C)(implicit cbf: scala.collection.generic.CanBuildFrom[CC[A],C,That]): That}
  • @Blaisorblade 哦,呵呵!时间久了,我什至没有意识到答案以new {开头。
【解决方案2】:

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

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)

【讨论】:

  • 有趣。我从来没有想过要获得IterableLike[_, Repr],并在Repr 上进行参数化。
  • 我想一个优势可能是ReprString 或其他一些未参数化的类型,如StringOps
  • 下拉到hasNext 这意味着它不适用于无限流。 huynhjl 的解决方案可以。
【解决方案3】:

老实说,我不确定它是如何工作的:

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 作为类型参数(CC[C])。我想使用breakOut 来获得正确的结果类型。我有点希望有一个 CanBuildFrom 隐含在范围内,但随后收到此错误消息:

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

然后诀窍是使用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
  }
}

注意this article 提供了类型构造器模式的一些背景知识。

【讨论】:

  • 如果您使用 zip 和 map 而不是下拉到迭代器级别,那么它可以与无限流一起使用。 +1!
猜你喜欢
  • 2010-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-27
  • 2017-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多