2011-12-20 11 views
32

Ci sono altre domande come Scala: What is the difference between Traversable and Iterable traits in Scala collections? e How would I get the sum of squares of two Lists in Scala? che risponde parzialmente alla domanda. Ho sentito che una domanda che copre tutto questo in un posto ha senso.Semantica di Scala traversabile, Iterable, Sequence, Stream e View?

+3

Sto votando per chiudere 1) perché questa non è una domanda specifica ma di gran lunga generale, 2) perché l'informazione rischia di diventare stantia quando Scala si evolve e 3) perché il caso generale è già ben definito da [La documentazione di Scala] (http://www.scala-lang.org/docu/files/collections-api/collections.html), che è probabilmente una risorsa migliore per indagini così ampie (e Scala, a differenza di molte altre lingue, ha una documentazione abbastanza ampia, anche se talvolta piuttosto impegnativa). – ig0774

+2

@ ig0774 2) Secondo me, anche i concetti chiave delle collezioni di scala devono essere modificati in modo significativo in un futuro osservabile. –

+7

@ ig0774 In contrasto con i tuoi tre punti, ho votato piuttosto che votare per chiudere perché 1) Ritengo che questa domanda sia generalmente valida e abbastanza tangibile da rispondere in modo sintetico, 2) perché si trova vicino al cuore di comprendere le attuali collezioni Scala biblioteca, e 3) la documentazione attuale, sebbene estesa, è molto diffusa; è difficile ottenere il quadro generale. –

risposta

33

Traversable è la parte superiore della gerarchia delle raccolte. Il suo metodo principale è 'foreach', quindi permette di fare qualcosa per ogni elemento della collezione.

Un Iterable può creare un Iterator, in base al quale foreach può essere implementato. Questo definisce un certo ordine degli elementi, sebbene quell'ordine possa cambiare per ogni Iterator.

Seq (uenza) è un Iterable in cui è fissato l'ordine degli elementi. Quindi ha senso parlare dell'indice di un elemento.

I flussi sono sequenze pigri. Cioè gli elementi di un flusso non possono essere calcolati prima di poterli accedere. Ciò rende possibile lavorare con sequenze infinite come la sequenza di tutti gli interi.

Visualizzazioni sono versioni non rigide delle raccolte. Metodi come filtro e mappa a vista eseguono solo le funzioni passate quando si accede al rispettivo elemento. Quindi una mappa su un'enorme collezione ritorna immediatamente perché crea solo un involucro attorno alla collezione originale. Solo quando si accede a un elemento, la mappatura viene effettivamente eseguita (per quell'elemento). Nota che View non è una classe, ma ci sono molte classi XxxView per varie raccolte.

+9

Le visualizzazioni non sono pigre, ma solo non rigide. La pigrizia richiede il caching, che i flussi fanno, ma le viste no. –

+0

Grazie per il chiarimento. risolto nella risposta. –

+4

@ DanielC.Sobral "La pigrizia richiede il caching" - ehm? –

2

Un commento Vorrei aggiungere degli stream contro iteratori. Entrambi gli stream e gli iteratori possono essere utilizzati per implementare raccolte lunghe, non rigide e potenzialmente infinite che non calcolano un valore finché non è necessario.

Tuttavia, esiste un problema complesso con "l'esecuzione prematura" che si verifica quando si esegue questa operazione, che può essere evitato utilizzando gli iteratori ma non i flussi e nel processo indica un'importante differenza semantica tra i due. Questo è forse illustrata nel modo più chiaro come segue:

def runiter(start: Int) { 
    // Create a stream that returns successive integers on demand, e.g. 3, 4, 5, .... 
    val iter = { 
    def loop(v: Int): Stream[Int] = { println("I computed a value", v); v} #:: loop(v+1) 
    loop(start) 
    } 
    // Now, sometime later, we retrieve the values .... 
    println("about to loop") 
    for (x <- iter) { 
    if (x < 10) println("saw value", x) else return 
    } 
} 

Questo codice crea un flusso infinito che parte da un dato valore e restituisce interi consecutivi. Viene utilizzato come supporto per codice più complesso che potrebbe, ad esempio, aprire una connessione Internet e restituire i valori dalla connessione, se necessario.

Risultato:

scala> runiter(3) 
(I computed a value,3) 
about to loop 
(saw value,3) 
(I computed a value,4) 
(saw value,4) 
(I computed a value,5) 
(saw value,5) 
(I computed a value,6) 
(saw value,6) 
(I computed a value,7) 
(saw value,7) 
(I computed a value,8) 
(saw value,8) 
(I computed a value,9) 
(saw value,9) 
(I computed a value,10) 

Nota attentamente come l'esecuzione necessaria per calcolare il primo valore si verifica prima che il luogo in cui siano effettivamente utilizzati i valori del flusso. Se questa esecuzione iniziale comporta, ad esempio, l'apertura di un file o di una connessione Internet e c'è un lungo ritardo dopo la creazione dello stream e prima che uno qualsiasi dei valori venga usato, questo può essere molto problematico - si finirà con un descrittore di file aperto seduto in giro, e peggio, la tua connessione Internet potrebbe scadere, causando il fallimento dell'intera operazione.

un semplice tentativo di risolvere il problema utilizzando un flusso di vuoto iniziale non funziona:

def runiter(start: Int) { 
    // Create a stream that returns successive integers on demand, e.g. 3, 4, 5, .... 
    val iter = { 
    def loop(v: Int): Stream[Int] = { println("I computed a value", v); v} #:: loop(v+1) 
    Stream[Int]() ++ loop(start) 
    } 
    // Now, sometime later, we retrieve the values .... 
    println("about to loop") 
    for (x <- iter) { 
    if (x < 10) println("saw value", x) else return 
    } 
} 

Risultato (come prima):

scala> runiter(3) 
(I computed a value,3) 
about to loop 
(saw value,3) 
(I computed a value,4) 
(saw value,4) 
(I computed a value,5) 
(saw value,5) 
(I computed a value,6) 
(saw value,6) 
(I computed a value,7) 
(saw value,7) 
(I computed a value,8) 
(saw value,8) 
(I computed a value,9) 
(saw value,9) 
(I computed a value,10) 

Tuttavia, è possibile risolvere questo problema cambiando il flusso in un iteratore con un iteratore vuoto iniziale, anche se è tutt'altro che scontato che questo sia il caso:

def runiter(start: Int) { 
    // Create an iterator that returns successive integers on demand, e.g. 3, 4, 5, .... 
    val iter = { 
    def loop(v: Int): Iterator[Int] = { println("I computed a value", v); Iterator(v)} ++ loop(v+1) 
    Iterator[Int]() ++ loop(start) 
    } 
    // Now, sometime later, we retrieve the values .... 
    println("about to loop") 
    for (x <- iter) { 
    if (x < 10) println("saw value", x) else return 
    } 
} 

Risultato:

scala> runiter(3) 
about to loop 
(I computed a value,3) 
(saw value,3) 
(I computed a value,4) 
(saw value,4) 
(I computed a value,5) 
(saw value,5) 
(I computed a value,6) 
(saw value,6) 
(I computed a value,7) 
(saw value,7) 
(I computed a value,8) 
(saw value,8) 
(I computed a value,9) 
(saw value,9) 
(I computed a value,10) 

Si noti che se non si aggiunge l'iteratore vuoto iniziale, vi imbatterete nello stesso problema di esecuzione prematura come con i flussi.

+3

è possibile modificare "val iter" in "def iter", nella versione di streaming. Questo farà il trucco. –