Tutorial de diseño de colecciones de Scala 2.8
scala-collections scala-2.8 (1)
Prefacio
Hay un recorrido de colección de 2.8 por Martin Odersky que probablemente sea su primera referencia. También se ha complementado con notas arquitectónicas , que serán de particular interés para aquellos que quieran diseñar sus propias colecciones.
El resto de esta respuesta fue escrita mucho antes de que existiera tal cosa (de hecho, antes de que se lanzara la versión 2.8.0).
Puede encontrar un documento sobre esto como Scala SID # 3 . Otros documentos en esa área también deberían ser interesantes para las personas interesadas en las diferencias entre Scala 2.7 y 2.8.
Voy a citar el artículo, selectivamente, y lo complementaré con algunos de mis pensamientos. También hay algunas imágenes, generadas por Matthias en decodified.com, y los archivos SVG originales se pueden encontrar here .
Las clases / rasgos de la colección en sí
En realidad, existen tres jerarquías de rasgos para las colecciones: una para colecciones mutables, una para colecciones inmutables y otra que no hace suposiciones sobre las colecciones.
También hay una distinción entre colecciones paralelas, seriales y quizás paralelas, que se introdujo con Scala 2.9. Hablaré de ellos en la próxima sección. La jerarquía descrita en esta sección se refiere exclusivamente a colecciones no paralelas .
La siguiente imagen muestra la jerarquía no específica introducida con Scala 2.8:
Todos los elementos mostrados son rasgos. En las otras dos jerarquías también hay clases que heredan directamente los rasgos, así como las clases que se pueden ver como pertenecientes a esa jerarquía mediante la conversión implícita a clases contenedoras. La leyenda de estos gráficos se puede encontrar después de ellos.
Gráfico para la jerarquía inmutable:
Gráfico para la jerarquía mutable:
Leyenda:
Aquí hay una representación abreviada de ASCII de la jerarquía de colecciones, para aquellos que no pueden ver las imágenes.
Traversable
|
|
Iterable
|
+------------------+--------------------+
Map Set Seq
| | |
| +----+----+ +-----+------+
Sorted Map SortedSet BitSet Buffer Vector LinearSeq
Colecciones Paralelas
Cuando Scala 2.9 introdujo colecciones paralelas, uno de los objetivos del diseño era hacer que su uso fuera lo más fluido posible. En los términos más simples, uno puede reemplazar una colección no paralela (serie) por una paralela y cosechar los beneficios al instante.
Sin embargo, dado que todas las colecciones hasta ese momento eran de serie, muchos algoritmos que las usaban suponían y dependían del hecho de que eran seriales. Las colecciones paralelas alimentadas a los métodos con tales suposiciones fallarían. Por esta razón, toda la jerarquía descrita en la sección anterior exige un procesamiento en serie .
Se crearon dos nuevas jerarquías para admitir las colecciones paralelas.
La jerarquía de colecciones paralelas tiene los mismos nombres para los rasgos, pero precedidos por Par
: ParIterable
, ParSeq
, ParMap
y ParSet
. Tenga en cuenta que no existe ParTraversable
, ya que cualquier colección que soporte acceso paralelo es capaz de soportar el rasgo ParIterable
más fuerte. Tampoco tiene algunos de los rasgos más especializados presentes en la jerarquía serial. Toda esta jerarquía se encuentra en el directorio scala.collection.parallel
.
Las clases que implementan colecciones paralelas también difieren, con ParHashMap
y ParHashSet
para colecciones paralelas mutables e inmutables, además de ParRange
y ParVector
implementando immutable.ParSeq
y ParArray
implementando mutable.ParSeq
.
También existe otra jerarquía que refleja los rasgos de las colecciones en serie y paralelas, pero con un prefijo Gen
: GenTraversable
, GenIterable
, GenSeq
, GenMap
y GenSet
. Estos rasgos son padres de colecciones paralelas y en serie. Esto significa que un método que toma un Seq
no puede recibir una colección paralela, pero se espera que un método que tome un GenSeq
funcione con colecciones seriales y paralelas.
Dada la estructura de estas jerarquías, el código escrito para Scala 2.8 era totalmente compatible con Scala 2.9 y exigía un comportamiento en serie. Sin ser reescrito, no puede aprovechar las colecciones paralelas, pero los cambios requeridos son muy pequeños.
Usar colecciones paralelas
Cualquier colección se puede convertir en una paralela llamando al método par
sobre ella. Del mismo modo, cualquier colección se puede convertir en una serie llamando al método seq
en ella.
Si la colección ya era del tipo solicitado (paralelo o en serie), no se realizará ninguna conversión. Sin embargo, si se llama a seq
en una colección paralela o par
en una colección en serie, se generará una nueva colección con la característica solicitada.
No confunda seq
, que convierte una colección en una colección no paralela, con toSeq
, que devuelve un Seq
creado a partir de los elementos de la colección. Llamar toSeq
en una colección paralela devolverá un ParSeq
, no una colección en serie.
Los principales rasgos
Si bien hay muchas clases de implementación y subtratos, existen algunos rasgos básicos en la jerarquía, cada uno de los cuales proporciona más métodos o garantías más específicas, pero reduce el número de clases que podrían implementarlos.
En las siguientes subsecciones, daré una breve descripción de los rasgos principales y la idea detrás de ellos.
Trait TraversableOnce
Este rasgo es muy parecido al rasgo Traversable
describe a continuación, pero con la limitación de que solo puedes usarlo una vez . Es decir, cualquier método invocado en TraversableOnce
puede volverlo inutilizable.
Esta limitación hace posible que los mismos métodos se compartan entre las colecciones y Iterator
. Esto hace posible que un método que funcione con un Iterator
pero que no utiliza métodos específicos de Iterator pueda realmente trabajar con cualquier colección, más iteradores, si se reescribe para aceptar TraversableOnce
.
Como TraversableOnce
unifica colecciones e iteradores, no aparece en los gráficos anteriores, que se refieren solo a las colecciones.
Traible Trasables
En la parte superior de la jerarquía de la colección , es Traible Traversable
. Su única operación abstracta es
def foreach[U](f: Elem => U)
La operación pretende atravesar todos los elementos de la colección y aplicar la operación dada f a cada elemento. La aplicación está hecha solo por su efecto secundario; de hecho, cualquier resultado de función de f es descartado por foreach.
Los objetos transversales pueden ser finitos o infinitos. Un ejemplo de un objeto atravesable infinito es la corriente de números naturales Stream.from(0)
. El método hasDefiniteSize
indica si una colección es posiblemente infinita. Si hasDefiniteSize
devuelve verdadero, la colección es hasDefiniteSize
finita. Si devuelve falso, la colección no ha sido completamente elaborada todavía, por lo que podría ser infinita o finita.
Esta clase define métodos que pueden implementarse de manera eficiente en términos de foreach
(más de 40 de ellos).
Rasgo Iterable
Este rasgo declara un iterator
método abstracto que devuelve un iterador que produce todos los elementos de la colección uno por uno. El método foreach
en Iterable
se implementa en términos de iterator
. Las subclases de Iterable
menudo anulan foreach con una implementación directa para la eficiencia.
Clase Iterable
también agrega algunos métodos menos utilizados a Traversable
, que se pueden implementar de manera eficiente solo si hay un iterator
disponible. Se resumen a continuación.
xs.iterator An iterator that yields every element in xs, in the same order as foreach traverses elements.
xs takeRight n A collection consisting of the last n elements of xs (or, some arbitrary n elements, if no order is defined).
xs dropRight n The rest of the collection except xs takeRight n.
xs sameElements ys A test whether xs and ys contain the same elements in the same order
Otros rasgos
Después de Iterable
, aparecen tres rasgos básicos que heredan de él: Seq
, Set
y Map
. Los tres tienen un método de apply
y los tres implementan el rasgo PartialFunction
, pero el significado de apply
es diferente en cada caso.
Confío en que el significado de Seq
, Set
y Map
sea intuitivo. Después de ellos, las clases se dividen en implementaciones específicas que ofrecen garantías particulares con respecto al rendimiento y los métodos que pone a su disposición como resultado de ello. También están disponibles algunos rasgos con mejoras adicionales, como LinearSeq
, IndexedSeq
y SortedSet
.
La lista a continuación puede mejorarse. Deja un comentario con sugerencias y lo arreglaré.
Clases base y rasgos
-
Traversable
- Clase de colección básica. Se puede implementar solo conforeach
.-
TraversableProxy
- Proxy para unTraversable
. Simplemente apunta a la verdadera colección. -
TraversableView
- A Traversable con algunos métodos no estrictos. -
TraversableForwarder
: reenvía la mayoría de los métodos aunderlying
, exceptotoString
,hashCode
,equals
,stringPrefix
,newBuilder
,view
y todas las llamadas creando un nuevo objeto iterable del mismo tipo. -
mutable.Traversable
eimmutable.Traversable
.Traversable
: lo mismo queTraversable
, pero restringe el tipo de colección. - Existen otras clases especiales
Iterable
, comoMetaData
. -
Iterable
: una colección para la cual se puede crear unIterator
(a través deliterator
).-
IterableProxy
,IterableView
,mutable
eimmutable.Iterable
.
-
-
-
Iterator
: un rasgo que no es descendiente deTraversable
. Definenext
yhasNext
.-
CountedIterator
-count
que define elIterator
, que devuelve los elementos vistos hasta el momento. -
BufferedIterator
- Definehead
, que devuelve el siguiente elemento sin consumirlo. - Existen otras clases de
Iterator
casos especiales, comoSource
.
-
Los mapas
-
Map
: unIterable
deTuple2
, que también proporciona métodos para recuperar un valor (el segundo elemento de la tupla) con una clave (el primer elemento de la tupla). Extiende la funciónPartialFunction
también.-
MapProxy
: unProxy
para unMap
. -
DefaultMap
: un rasgo que implementa algunos de los métodos abstractos deMap
. -
SortedMap
- UnMap
cuyas claves están ordenadas.-
immutable.SortMap
-
immutable.TreeMap
- Una clase que implementaimmutable.SortedMap
.
-
-
-
immutable.Map
-
immutable.MapProxy
-
immutable.HashMap
- Una clase que implementaimmutable.Map
través de la clave hash. -
immutable.IntMap
- Una clase que implementaimmutable.Map
especializada para clavesInt
. Utiliza un árbol basado en los dígitos binarios de las teclas. -
immutable.ListMap
- Una clase que implementaimmutable.Map
través de listas. -
immutable.LongMap
- Una clase que implementaimmutable.Map
especializada para clavesLong
. VerIntMap
. - Hay clases adicionales optimizadas para una cantidad específica de elementos.
-
-
mutable.Map
-
mutable.HashMap
- Una clase que implementamutable.Map
través de la clave hash. -
mutable.ImmutableMapAdaptor
- Una clase que implementa unmutable.Map
desde unmutable.Map
immutable.Map
existente. -
mutable.LinkedHashMap
-? -
mutable.ListMap
- Una clase que implementamutable.Map
través de listas. -
mutable.MultiMap
- Una clase que acepta más de un valor distinto para cada clave. -
mutable.ObservableMap
- Una mezcla que, cuando se mezcla con unMap
, publica eventos a los observadores a través de una interfaz dePublisher
. -
mutable.OpenHashMap
- Una clase basada en un algoritmo hash abierto. -
mutable.SynchronizedMap
- Una mezcla que se debe mezclar con unMap
para proporcionar una versión con métodos sincronizados. -
mutable.MapProxy
.
-
-
Las secuencias
-
Seq
- Una secuencia de elementos. Uno asume una repetición de tamaño y elemento bien definida. Extiende la funciónPartialFunction
también.-
IndexedSeq
- Secuencias que admiten O (1) acceso a elementos y O (1) cálculo de longitud.-
IndexedSeqView
-
immutable.PagedSeq
- Una implementación deIndexedSeq
donde los elementos son producidos bajo demanda por una función que pasa a través del constructor. -
immutable.IndexedSeq
-
immutable.Range
: una secuencia delimitada de enteros, cerrada en el extremo inferior, abierta en el extremo superior y con un paso.-
immutable.Range.Inclusive
: unRange
cerrado en el extremo superior también. -
immutable.Range.ByOne
- UnRange
cuyo paso es 1.
-
-
immutable.NumericRange
: una versión más genérica deRange
que funciona con cualquierIntegral
.-
immutable.NumericRange.Inclusive
,immutable.NumericRange.Exclusive
. -
immutable.WrappedString
,immutable.RichString
- Wrappers que permite ver unString
como unSeq[Char]
, mientras se conservan los métodosString
. No estoy seguro de cuál es la diferencia entre ellos.
-
-
-
mutable.IndexedSeq
-
mutable.GenericArray
- Una estructura de matriz basada enSeq
. Tenga en cuenta que la "clase"Array
es Java''sArray
, que es más un método de almacenamiento de memoria que una clase. -
mutable.ResizableArray
- Clase interna utilizada por clases basadas en matrices de tamaño variable. -
mutable.PriorityQueue
,mutable.SynchronizedPriorityQueue
- Clases que implementan colas priorizadas - colas en las que los elementos se quitan de la cola según unOrdering
primero y el orden de la última cola. -
mutable.PriorityQueueProxy
- unProxy
abstracto paraPriorityQueue
.
-
-
-
LinearSeq
- Un rasgo para secuencias lineales, con tiempo eficiente paraisEmpty
,head
ytail
.-
immutable.LinearSeq
-
immutable.List
: una implementación de lista inmutable, de enlace único. -
immutable.Stream
- A lazy-list. Sus elementos solo se computan a pedido, pero se memorizan (guardan en la memoria) después. Puede ser teóricamente infinito.
-
-
mutable.LinearSeq
-
mutable.DoublyLinkedList
- Una lista con mutableprev
,head
(elem
) ytail
(next
). -
mutable.LinkedList
- Una lista conhead
mutable (elem
) ytail
(next
). -
mutable.MutableList
- Una clase utilizada internamente para implementar clases basadas en listas mutables.-
mutable.Queue
,mutable.QueueProxy
- Una estructura de datos optimizada para operaciones FIFO (First-In, First-Out). -
mutable.QueueProxy
- UnProxy
para unmutable.Queue
.
-
-
-
-
SeqProxy
,SeqView
,SeqForwarder
-
immutable.Seq
-
immutable.Queue
: una clase que implementa una estructura de datos optimizada para FIFO (First-In, First-Out). No existe una superclase común de colasmutable
eimmutable
. -
immutable.Stack
- Una clase que implementa una estructura de datos optimizada para LIFO (Last-In, First-Out). No existe una superclase común de ambas pilasmutable
immutable
. -
immutable.Vector
-? -
scala.xml.NodeSeq
- Una clase XML especializada que extiendeimmutable.Seq
. -
immutable.IndexedSeq
- Como se ve arriba. -
immutable.LinearSeq
- Como se ve arriba.
-
-
mutable.ArrayStack
- Una clase que implementa una estructura de datos optimizada para LIFO utilizando matrices. Supuestamente significativamente más rápido que una pila normal. -
mutable.Stack
,mutable.SynchronizedStack
- Clases que implementan una estructura de datos optimizada para LIFO. -
mutable.StackProxy
- UnProxy
para unmutable.Stack
.. -
mutable.Seq
-
mutable.Buffer
- Secuencia de elementos que pueden modificarse añadiendo, anteponiendo o insertando nuevos miembros.-
mutable.ArrayBuffer
- Una implementación de la clasemutable.Buffer
, con tiempo amortizado constante para las operaciones demutable.Buffer
, actualización y acceso aleatorio. Tiene algunas subclases especializadas, comoNodeBuffer
. -
mutable.BufferProxy
,mutable.SynchronizedBuffer
. -
mutable.ListBuffer
- Un buffer respaldado por una lista. Proporciona anexar y anexar un tiempo constante, siendo la mayoría de las demás operaciones lineales. -
mutable.ObservableBuffer
- Un rasgo de mixin que, cuando se mezcla con unBuffer
, proporciona eventos de notificación a través de las interfaces de unPublisher
. -
mutable.IndexedSeq
- Como se ve arriba. -
mutable.LinearSeq
- Como se ve arriba.
-
-
-
Los conjuntos
-
Set
: un conjunto es una colección que incluye a lo sumo uno de cualquier objeto.-
BitSet
- Un conjunto de enteros almacenados como un conjunto de bits.-
immutable.BitSet
-
mutable.BitSet
-
-
SortedSet
- Un conjunto cuyos elementos están ordenados.-
immutable.SortedSet
-
immutable.TreeSet
: una implementación deSortedSet
basado en un árbol.
-
-
-
SetProxy
: unProxy
para unSet
. -
immutable.Set
-
immutable.HashSet
: una implementación deSet
basada en hash de elementos. -
immutable.ListSet
: una implementación deSet
basada en listas. - Existen clases de conjuntos adicionales para proporcionar implementaciones optimizadas para conjuntos de 0 a 4 elementos.
-
immutable.SetProxy
- UnProxy
para unSet
inmutable.
-
-
mutable.Set
-
mutable.HashSet
- Una implementación deSet
basada en hash de elementos. -
mutable.ImmutableSetAdaptor
- Una clase que implementa unSet
mutable desde unSet
inmutable. -
LinkedHashSet
- Una implementación deSet
basada en listas. -
ObservableSet
- Un rasgo de mixin que, cuando se mezcla con unSet
, proporciona eventos de notificación a través de una interfaz dePublisher
. -
SetProxy
: unProxy
para unSet
. -
SynchronizedSet
- Un rasgo mixin que, cuando se mezcla con unSet
, proporciona eventos de notificación a través de una interfaz dePublisher
.
-
-
- Por qué existen las clases Like (p. Ej. TraversableLike)
Esto se hizo para lograr la máxima reutilización del código. La implementación genérica concreta para las clases con una cierta estructura (un traversable, un mapa, etc.) se realiza en las clases Me gusta. Las clases destinadas al consumo general, entonces, anulan los métodos seleccionados que pueden optimizarse.
- Para qué sirven los métodos complementarios (por ejemplo, List.companion)
El generador de las clases, es decir, el objeto que sabe cómo crear instancias de esa clase de una manera que pueda ser utilizada por métodos como el map
, se crea mediante un método en el objeto complementario. Entonces, para construir un objeto de tipo X, necesito obtener ese constructor del objeto compañero de X. Desafortunadamente, no hay forma, en Scala, de pasar de la clase X al objeto X. Por eso, hay un método definido en cada instancia de X, companion
, que devuelve el objeto complementario de la clase X.
Si bien puede haber algún uso para dicho método en los programas normales, su objetivo es permitir la reutilización del código en la biblioteca de la colección.
- Cómo sé qué objetos implícitos están en el alcance en un punto dado
Se supone que no debes preocuparte por eso. Están implícitos precisamente para que no necesites averiguar cómo hacerlo funcionar.
Estas implicaciones existen para permitir que los métodos en las colecciones se definan en las clases principales pero aún devuelvan una colección del mismo tipo. Por ejemplo, el método de map
se define en TraversableLike
, pero si lo usaste en una List
, obtendrás una List
nuevo.
Después de mi confusión sin aliento , ¿cuáles son algunos buenos recursos que explican cómo se ha estructurado la nueva biblioteca de colecciones de Scala 2.8 ? Me interesa encontrar información sobre cómo encajan los siguientes puntos:
- Las clases / características de la colección en sí (p. Ej.
List
,Iterable
) - Por qué existen las clases Like (p. Ej.
TraversableLike
) - Para qué sirven los métodos complementarios (por ejemplo,
List.companion
) - Cómo sé qué objetos
implicit
están en el alcance en un punto dado