Как написать метод zipWith, который возвращает тот же тип коллекции, что и те, которые ему переданы?

Я дошел до этого:

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

Ответы

Ответ 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 - so 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)

Ответ 2

Проблема выше в том, что ваше неявное преобразование collectionExtras заставляет полученный объект потерять информацию о типе. В частности, в вышеприведенном решении конкретный тип коллекции теряется, потому что вы передаете ему объект типа Iterable[A] - с этого момента компилятор больше не знает реальный тип xs. Хотя конструктор factory 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 - обратите внимание, что построитель factory 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)

Ответ 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)

Я вроде обезьяна заплатил этот ответ от 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
  }
}

Примечание эта статья содержит некоторые сведения о шаблоне конструктора типов.