Il problema di cui sopra è che la vostra conversione implicita collectionExtras
fa sì che l'oggetto ottenuto di perdere le informazioni sul tipo. In particolare, nella soluzione di cui sopra, il tipo di raccolta di calcestruzzo viene perso perché lo si passa di un oggetto di tipo Iterable[A]
- da questo momento in poi, il compilatore non conosce più il tipo reale di xs
. Sebbene la factory di produzione CanBuildFrom
assicuri che il tipo dinamico della raccolta sia corretto (si ottiene veramente un Vector
), staticamente, il compilatore sa solo che zipWith
restituisce qualcosa che è un Iterable
.
Per risolvere questo problema, anziché avere la conversione implicita prendere un Iterable[A]
, lasciarlo prendere uno IterableLike[A, Repr]
. Perché?
Iterable[A]
solito dichiarata come qualcosa di simile:
Iterable[A] extends IterableLike[A, Iterable[A]]
La differenza con Iterable
è che questa IterableLike[A, Repr]
mantiene il tipo di raccolta di cemento come Repr
. La maggior parte delle collezioni di cemento, oltre a mescolare in Iterable[A]
, si mescolano anche nel tratto IterableLike[A, Repr]
, sostituendo il Repr
con il loro tipo concreto, come di seguito:
Vector[A] extends Iterable[A] with IterableLike[A, Vector[A]]
Possono farlo perché il tipo di parametro Repr
è dichiarato come covariante.
Per farla breve, utilizzando IterableLike
cause si conversione implicita per mantenere le informazioni sul tipo di raccolta di cemento (cioè Repr
) intorno e usarlo quando si definiscono zipWith
- notare che la fabbrica del costruttore CanBuildFrom
conterrà ora Repr
invece di Iterable[A]
per la primo parametro tipo, provocando l'oggetto implicita appropriata da risolvere:
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
}
}
Leggendo il tuo formulazione questione più attentamente ("come scrivere un metodo zipWith che restituisce lo stesso tipo di raccolta, come quelli passati ad essa"), mi sembra che tu voglia avere lo stesso tipo di raccolta come quelli passati a zipWith
, non alla conversione implicita, che è lo stesso tipo di ys
.
stessi motivi di prima, vedi soluzione qui di seguito:
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 i risultati:
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 valore di un tipo strutturale, che viene richiamato tramite riflessione; per evitare ciò, dichiarare le firme in un tratto ZipWith e restituire un'istanza di questa caratteristica. Questo vale per la domanda e per tutte le soluzioni. – Blaisorblade