2010-10-09 7 views
10

ho raggiunto fino a questo punto:Come scrivere un metodo zipWith che restituisce lo stesso tipo di collezione di quelli passati ad esso?

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) 

Ora il problema è che al di sopra metodo restituisce sempre un Iterable. Come faccio a restituire la raccolta di tipo come quella che gli è stata passata? (in questo caso, Vector) Grazie.

+0

'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

risposta

9

Ti sei avvicinato abbastanza. Basta una piccola modifica in due linee:

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 
    } 
} 

In primo luogo, è necessario ottenere il tipo di raccolta essere passato, così ho aggiunto CC[A] come un parametro di tipo. Inoltre, tale raccolta deve essere in grado di "riprodursi" stessa - che è garantita dal secondo parametro di tipo IterableLike - quindi CC[A] <: IterableLike[A, CC[A]]. Si noti che questo secondo parametro di IterableLike è Repr, precisamente il tipo di xs.repr.

Naturalmente, CanBuildFrom deve ricevere CC[A] anziché Iterable[A]. E questo è tutto ciò che c'è da fare.

E il risultato:

scala> Vector(2, 2, 2).zipWith(Vector(4, 4, 4))(_ * _) 
res0: scala.collection.immutable.Vector[Int] = Vector(8, 8, 8) 
+0

si riduce a 'hasNext' e tutto ciò significa che non funziona su Stream infiniti. la soluzione di huynhjl. –

+0

Ho due problemi con la tua risposta: a) scrivi 'CC [A]', che è problematico quando si comprimono gli elenchi (c'è un motivo per cui la libreria di collezioni Scala non usa i tipi più elevati in questo modo, e sono sicuro sai). b) Anche questa risposta rende il tipo di ritorno 'collectionExtras' essere di tipo strutturale (e quindi inefficiente). – Blaisorblade

+0

@Blaisorblade se si guarda il mio commento ad axel22, al momento non mi è venuta l'altra soluzione. Per quanto riguarda il tipo strutturale, non vedo come si evolverà - i tipi strutturali possono essere complicati in questo modo. : -/ –

8

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) 
+1

Interessante. Non mi è mai venuto in mente di ottenere un 'IterableLike [_, Repr]' e di parametrizzare su 'Repr'. –

+0

Immagino che un vantaggio potrebbe essere nel caso in cui 'Repr' sia un' String', o qualche altro tipo non parametrizzato, come in 'StringOps'. – axel22

+0

si riduce a 'hasNext' e tutto ciò significa che non funziona su Stream infiniti. la soluzione di huynhjl. –

5

Per essere onesti non sono sicuro di come funziona davvero:

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) 

Ho una specie di scimmia con patch this answer from retronym finché non ha funzionato!

Fondamentalmente, voglio utilizzare il costruttore CC[X] tipo per indicare che zipWith dovrebbe restituire il tipo di raccolta xs ma con C come parametro tipo (CC[C]). E voglio usare breakOut per ottenere il giusto tipo di risultato. I sorta di speravo che ci fosse un CanBuildFrom implicita nella portata, ma poi ha ottenuto questo messaggio di errore:

required: scala.collection.generic.CanBuildFrom[Iterable[(A, B)],C,CC[C]] 

Il trucco era quindi di utilizzare Nothing invece di Iterable[(A, B)]. Immagino che implicito sia definito da qualche parte ...

Inoltre, mi piace pensare al tuo zipWith come zip e poi a map, quindi ho cambiato l'implementazione. Qui è con l'implementazione:

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 
    } 
} 

Nota this article fornisce alcuni retroscena sul modello tipo di costruzione.

+0

se usi zip e mappa invece di scendere a livello Iterator, quindi funziona con Stream infiniti. +1! –

+0

funziona con 'classe implicita' in Scala 2.10! vedi http://gist.github.com/2942990 –

Problemi correlati