concatenate scala scala-collections

concatenate - Semántica de Scala Traversable, Iterable, Sequence, Stream y View?



scala mutable collections (2)

Hay otras preguntas, como Scala: ¿Cuál es la diferencia entre los rasgos de Traversable y Iterable en las colecciones de Scala? y ¿Cómo obtendría la suma de cuadrados de dos Listas en Scala? eso responde la pregunta parcialmente. Sentí que una pregunta que cubre todo esto en un lugar tiene sentido.


Un comentario que me gustaría agregar sobre streams vs. iteradores. Tanto las secuencias como los iteradores se pueden usar para implementar colecciones largas, no estrictas, potencialmente infinitas que no computan un valor hasta que se necesita.

Sin embargo, hay un problema complicado con la "ejecución prematura" que se produce al hacer esto, que se puede evitar utilizando iteradores pero no flujos, y en el proceso se señala una importante diferencia semántica entre los dos. Esto se ilustra tal vez más claramente de la siguiente manera:

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

Este código crea una secuencia infinita que comienza en un valor dado y devuelve enteros sucesivos. Se utiliza como un sustituto de código más complejo que podría, por ejemplo, abrir una conexión a Internet y devolver valores de la conexión según sea necesario.

Resultado:

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)

Observe con atención cómo se produce la ejecución requerida para calcular el primer valor ANTES del lugar donde realmente se usan los valores de la secuencia. Si esta ejecución inicial implica, por ejemplo, abrir un archivo o conexión a Internet y hay una gran demora después de crear la secuencia y antes de que se use cualquiera de los valores, esto puede ser muy problemático: terminará con un descriptor de archivo abierto sentado alrededor, y lo que es peor, su conexión a Internet podría expirar, haciendo que todo falle.

Un simple intento de arreglarlo usando una secuencia vacía inicial no funciona:

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

Resultado (igual que antes):

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)

Sin embargo, puede solucionar esto cambiando la secuencia a un iterador con un iterador vacío inicial, aunque está lejos de ser obvio que este es el 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 } }

Resultado:

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)

Tenga en cuenta que si no agrega el iterador vacío inicial, se encontrará con el mismo problema de ejecución prematura que con las transmisiones.


Traversable es la parte superior de la jerarquía de colecciones. Su método principal es ''foreach'' por lo que permite hacer algo para cada elemento de la colección.

Un Iterable puede crear un iterador, en función de qué foreach se puede implementar. Esto define cierto orden de los elementos, aunque ese orden puede cambiar para cada iterador.

Seq (uence) es un Iterable donde el orden de los elementos es fijo. Por lo tanto, tiene sentido hablar sobre el índice de un elemento.

Las secuencias son secuencias perezosas. Es decir, es posible que los elementos de una secuencia no se computen antes de acceder a ellos. Esto hace posible trabajar con secuencias infinitas como la secuencia de todos los enteros.

Las vistas son versiones no estrictas de colecciones. Los métodos como filtro y mapa en vista solo ejecutan las funciones pasadas cuando se accede al elemento respectivo. Por lo tanto, un mapa de una enorme colección regresa inmediatamente porque solo crea un envoltorio alrededor de la colección original. Solo cuando uno accede a un elemento, la asignación se ejecuta realmente (para ese elemento). Tenga en cuenta que View no es una clase, pero hay muchas clases de XxxView para varias colecciones.