El problema anterior es que su conversión implícita collectionExtras
hace que el objeto obtenido de perder información de tipo. En particular, en la solución anterior, el tipo de colección de hormigón se pierde porque se le pasa un objeto del tipo Iterable[A]
- a partir de este momento, el compilador ya no conoce el tipo real de xs
. Aunque la fábrica de constructores CanBuildFrom
asegura programáticamente que el tipo dinámico de la colección es correcto (realmente obtienes un Vector
), estáticamente, el compilador solo sabe que zipWith
devuelve algo que es un Iterable
.
Para resolver este problema, en lugar de tener la conversión implícita tomar un Iterable[A]
, que tome un IterableLike[A, Repr]
. ¿Por qué?
Iterable[A]
normalmente se declara como algo parecido a:
Iterable[A] extends IterableLike[A, Iterable[A]]
La diferencia con Iterable
es que este IterableLike[A, Repr]
mantiene el tipo de colección concreta como Repr
. La mayoría de las colecciones de hormigón, además de la mezcla en Iterable[A]
, también mezclan en el rasgo IterableLike[A, Repr]
, en sustitución del Repr
con su tipo de hormigón, como a continuación:
Vector[A] extends Iterable[A] with IterableLike[A, Vector[A]]
que pueden hacer esto porque Repr
tipo de parámetro se declara como covariante.
Para resumir, utilizando IterableLike
causas que la conversión implícita para mantener la información de tipo de recogida de concreto (es decir Repr
) alrededor y utilizarlo cuando se define zipWith
- tenga en cuenta que la fábrica constructor CanBuildFrom
contendrá ahora Repr
en lugar de Iterable[A]
para el primer parámetro tipo, haciendo que el objeto implícito adecuado para ser resuelto:
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
}
}
la lectura de su formulación de las preguntas con más cuidado ("¿Cómo escribir un método zipWith que devuelve el mismo tipo de colección como los que se le pasa"), me parece que quieres tener el mismo tipo de recolección iones como los pasados a zipWith
, no a la conversión implícita, que es del mismo tipo que ys
.
mismas razones que antes, ver más abajo solución:
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
}
}
Con los resultados:
scala> immutable.Vector(2, 2, 2).zipWith(mutable.ArrayBuffer(4, 4, 4))(_ * _)
res1: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(8, 8, 8)
'new {def foo =}' produce un valor de tipo estructural, que se invoca mediante reflexión; para evitar esto, declare las firmas en un rasgo ZipWith y devuelva una instancia de este rasgo. Esto se aplica a la pregunta y a todas las soluciones. – Blaisorblade