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

发布于 2024-09-26 14:19:37 字数 781 浏览 0 评论 0原文

我已经到达这里了:

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

I have reached this far:

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)

Now the problem is that above method always returns an Iterable. How do I make it return the collection of type as those passed to it? (in this case, Vector) Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

调妓 2024-10-03 14:19:37

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

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)

You got close enough. Just a minor change in two lines:

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
  }
}

First, you need to get the collection type being passed, so I added CC[A] as a type parameter. Also, that collection needs to be able to "reproduce" itself -- that is guaranteed by the second type parameter of IterableLike -- so CC[A] <: IterableLike[A, CC[A]]. Note that this second parameter of IterableLike is Repr, precisely the type of xs.repr.

Naturally, CanBuildFrom needs to receive CC[A] instead of Iterable[A]. And that's all there is to it.

And the result:

scala> Vector(2, 2, 2).zipWith(Vector(4, 4, 4))(_ * _)
res0: scala.collection.immutable.Vector[Int] = Vector(8, 8, 8)
黎歌 2024-10-03 14:19:37

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

The problem above is that your implicit conversion collectionExtras causes the obtained object to lose type information. In particular, in the solution above, the concrete collection type is lost because you're passing it an object of type Iterable[A] - from this point on, the compiler no longer knows the real type of xs. Although the builder factory CanBuildFrom programatically ensures that the dynamic type of the collection is correct (you really get a Vector), statically, the compiler knows only that zipWith returns something that is an Iterable.

To solve this problem, instead of having the implicit conversion take an Iterable[A], let it take an IterableLike[A, Repr]. Why?

Iterable[A] is usually declared as something like:

Iterable[A] extends IterableLike[A, Iterable[A]]

The difference with Iterable is that this IterableLike[A, Repr] keeps the concrete collection type as Repr. Most concrete collections, in addition to mixing in Iterable[A], also mix in the trait IterableLike[A, Repr], replacing the Repr with their concrete type, like below:

Vector[A] extends Iterable[A] with IterableLike[A, Vector[A]]

They can do this because type parameter Repr is declared as covariant.

Long story short, using IterableLike causes you implicit conversion to keep the concrete collection type information (that is Repr) around and use it when you define zipWith - note that the builder factory CanBuildFrom will now contain Repr instead of Iterable[A] for the first type parameter, causing the appropriate implicit object to be resolved:

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
  }
}

Reading your question formulation more carefully ("How to write a zipWith method that returns the same type of collection as those passed to it?"), it seems to me that you want to have the same type of collection as those passed to zipWith, not to the implicit conversion, that is the same type asys.

Same reasons as before, see solution below:

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
  }
}

With results:

scala> immutable.Vector(2, 2, 2).zipWith(mutable.ArrayBuffer(4, 4, 4))(_ * _)
res1: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(8, 8, 8)
嗳卜坏 2024-10-03 14:19:37

老实说,我不确定这到底是如何工作的:

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)]。我猜隐式是在某处定义的...

另外,我喜欢将您的 zipWith 视为 zip ,然后是地图,所以我更改了执行。这是您的实现:

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
  }
}

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

To be honest I'm not sure how that really works:

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)

I sort of monkey patched this answer from retronym until it worked!

Basically, I want to use the CC[X] type constructor to indicate that zipWith should return the collection type of xs but with C as the type parameter (CC[C]). And I want to use breakOut to get the right result type. I sort of hoped that there was a CanBuildFrom implicit in scope but then got this error message:

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

The trick was then to use Nothing instead of Iterable[(A, B)]. I guess that implicit is defined somewhere...

Also, I like to think of your zipWith as zip and then map, so I changed the implementation. Here is with your implementation:

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
  }
}

Note this article provides some background on the type constructor pattern.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文