Как написать метод 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
}
}
Примечание эта статья содержит некоторые сведения о шаблоне конструктора типов.