Las colecciones en Scala son una de las partes más interesantes y potentes del lenguaje, especialmente por la gran cantidad de funcionalidades que nos ofrecen desde un enfoque funcional. No solo se trata de un conjunto bien organizado de clases con métodos comunes que facilitan su uso, sino también de una API rica en funciones para filtrar, mapear y transformar datos de manera eficiente.
En Scala, las colecciones se dividen principalmente en dos grandes grupos: las colecciones inmutables y las colecciones mutables. Es importante aclarar que esta distinción no es exactamente la misma que la que hacemos cuando hablamos de variables mutables o inmutables. Por ejemplo, podemos tener una variable inmutable que apunta a un objeto mutable. La inmutabilidad de la variable solo significa que no podemos reasignar ese identificador a otro objeto, pero el objeto en sí puede cambiar su estado interno si es mutable.
Cuando hablamos de colecciones inmutables, nos referimos a aquellas en las que, una vez creados sus elementos, no podemos modificar su contenido. Cualquier operación que parezca modificar la colección, como agregar un elemento o concatenar dos listas, en realidad devuelve una nueva colección con los cambios aplicados, dejando intacta la original. Por ejemplo, si tenemos una lista inmutable y queremos añadir un elemento, no alteramos la lista original, sino que creamos una nueva lista que incluye ese elemento.
Por otro lado, las colecciones mutables sí permiten modificar su contenido directamente. Podemos añadir elementos a una lista mutable, cambiar los pares clave-valor de un mapa mutable, y en general alterar el estado interno de la colección sin necesidad de crear nuevas instancias. Esto puede ser útil en ciertos contextos donde la eficiencia o la simplicidad de la mutabilidad es preferible.
En cuanto a la organización de estas colecciones en Scala, encontramos que están distribuidas en paquetes específicos. El paquete scala.collection es el principal, y dentro de él hay subpaquetes como immutable, mutable y generic. Por defecto, Scala importa automáticamente las colecciones inmutables, por lo que podemos crear listas, conjuntos y mapas inmutables sin necesidad de importar nada adicional. Sin embargo, para usar las colecciones mutables, debemos importar explícitamente el paquete scala.collection.mutable.
El mecanismo de importación en Scala es similar al de Java, pero con algunas particularidades y azúcar sintáctico. Podemos importar todo un paquete con un guion bajo (_), o importar solo las clases que nos interesan. Por ejemplo, para usar un conjunto mutable, podemos importar todo el paquete mutable y luego referirnos a mutable.Set para dejar claro que estamos usando la versión mutable. Esto ayuda a evitar confusiones entre las colecciones mutables e inmutables.
En cuanto a la jerarquía de colecciones, todas derivan de un trait llamado Traversable, que define operaciones básicas para recorrer una colección, como foreach. De Traversable hereda Iterable, que nos permite iterar sobre los elementos de la colección. Esta jerarquía es fundamental para entender cómo funcionan los bucles y las operaciones sobre colecciones en Scala.
A partir de Iterable, la jerarquía se ramifica en varios tipos principales de colecciones. Por un lado, tenemos los Set, que representan conjuntos donde no se permiten elementos duplicados. Si hemos trabajado con HashSet en Java, el concepto es muy similar. Por otro lado, están los Map, que son colecciones de pares clave-valor, donde cada clave es única y se asocia a un valor. Finalmente, la categoría más amplia es la de las secuencias (Seq), que representan colecciones ordenadas de elementos.
Dentro de las secuencias, hay dos subtipos importantes: IndexedSeq y LinearSeq. Las IndexedSeq son secuencias en las que los elementos están posicionados y podemos acceder a ellos por índice de forma eficiente. Aquí entran los vectores, los rangos y los arrays. Por otro lado, las LinearSeq son secuencias lineales donde la posición se define de forma más simple, como en las listas enlazadas, pilas o colas. Estas estructuras permiten operaciones típicas de cabeza y cola, pero no necesariamente acceso eficiente por índice.
Esta organización jerárquica nos permite elegir la colección que mejor se adapte a nuestras necesidades, ya sea que necesitemos inmutabilidad, mutabilidad, acceso rápido por índice o estructuras lineales para procesamiento secuencial.
En los próximos pasos, podemos profundizar en las características específicas de cada tipo de colección, cómo manipularlas y aprovechar al máximo las funciones que Scala nos ofrece para trabajar con ellas de forma funcional y eficiente. Así, podremos manejar listas, mapas y conjuntos con confianza y claridad, aprovechando la potencia y flexibilidad que nos brinda Scala.