Как написать метод zipWith, который возвращает тот же тип коллекции, что и передаваемые ему? - PullRequest
11 голосов
/ 09 октября 2010

Я достиг этого далеко:

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) Спасибо.

Ответы [ 3 ]

9 голосов
/ 09 октября 2010

Вы достаточно близко. Небольшое изменение в двух строках:

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)
8 голосов
/ 09 октября 2010

Проблема выше в том, что ваше неявное преобразование 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)
5 голосов
/ 09 октября 2010

Если честно, я не уверен, как это на самом деле работает:

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 должен возвращать тип коллекции 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
  }
}

Примечание В этой статье приведена некоторая справочная информация о шаблоне конструктора типов.

...