11

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.

4

3 に答える 3

9

あなたは十分に近づきました。2行のわずかな変更:

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]、タイプパラメータとして追加しました。また、そのコレクションは、それ自体を「再現」できる必要があります。これは、の2番目の型パラメーターによって保証されIterableLikeますCC[A] <: IterableLike[A, CC[A]]。のこの2番目のパラメータ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)
于 2010-10-09T15:17:00.810 に答える
8

上記の問題は、暗黙の変換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含まれるようになり、適切な暗黙のオブジェクトが解決される:ReprIterable[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)

それが機能するまで、レトロニムからこの回答にパッチを当てた猿のようなものです!

基本的に、CC[X]型コンストラクターを使用してzipWith、 のコレクション型を返す必要があることを示しますxsC、型パラメーター ( CC[C]) として使用します。breakOutそして、正しい結果タイプを取得するために使用したいと思います。CanBuildFromスコープに暗黙的なものがあることを期待していましたが、次のエラーメッセージが表示されました。

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

トリックは、のNothing代わりに使用することでしIterable[(A, B)]た。私は暗黙がどこかに定義されていると思います...

また、私はあなたのことzipWithを aszipと 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 に答える