logoImagina
iconCurso
Te recomendamos nuestro curso de Scala
Descubre el curso de Scala
Ir al curso

Novedades de Scala 3: Descubre esta Versión

iconImage
Escrito por Equipo de Imagina
Actualizado el 09-09-2024
Duración: 30 min

Todas las novedades en Scala

Bienvenidos a esta emocionante entrada donde exploraremos las últimas novedades en el mundo de Scala. Scala, un lenguaje de programación funcional y orientado a objetos, continúa evolucionando y sorprendiendo a la comunidad de desarrolladores con sus nuevas características y mejoras. En esta ocasión, descubriremos las actualizaciones más recientes que han llegado a Scala, desde las mejoras en la sintaxis y el rendimiento hasta las nuevas herramientas que han surgido. ¡Prepárate para sumergirte en el fascinante universo de Scala y descubrir las últimas innovaciones que lo hacen aún más poderoso y versátil!

En caso de que quieras formarte en el uso de Scala te recomendamos visitar nuestro curso de Scala.

Pantalla de ordenador mostrando la nueva versión de Scala 3

Intersecciones y uniones

Los tipos de intersección y unión son ligeramente diferentes:

Empecemos por los tipos de unión. Un tipo de unión, expresado como A | B, representa una unión "verdadera", a diferencia de una unión disjunta, la cual separa entre “izquierda” y “derecha”. En este caso no hay izquierda y derecha, por lo que literalmente podemos hacer esto:

val a: A | B = new A()

val b: A | B = new B()

Como son commutativas, A | B es lo mismo que B | A.

Los tipos de intersección (expresados cómo A y B) representan un concepto dual para los tipos de unión, y al igual que con los tipos de unión, ya hay construcciones similares presentes en Scala. Ya podemos combinar los rasgos B y C en alguna clase / rasgo / objeto A, lo que resulta en el tipo de intersección "A con B con C".

La principal diferencia entre estas combinaciones y los tipos de intersección que estamos obteniendo en Scala 3 es la conmutatividad: A con B no es lo mismo que B con A, al menos desde la perspectiva del sistema de tipos, mientras que A y B y B y A son lo mismo y se pueden usar indistintamente.

Parámetros Trait

Como sabemos los trait no pueden tener parámetros, sino que usamos clases abstractas para ello. Sin embargo, las clases abstractas no se pueden mezclar en diferentes partes de la jerarquía de clases.

Ahora los traits también obtienen parámetros, así que podemos usar una sintaxis como esta:

trait Foo(val s: String) {

...

}

Esto realmente no compilaría ya que tanto Foo ("a") como Foo ("b") se han mezclado en algún momento. Para que esto funcione hay que seguir las siguientes reglas:

  • Solo las clases pueden pasar argumentos a sus traits principales; Los traits no pueden pasar argumentos a los rasgos.
  • Cuando una clase C extiende un trait T con parámetros, debe proporcionar los argumentos a T, excepto en el caso de que C tenga una superclase que también extienda de T; en ese caso, es la superclase la que debe proporcionar los argumentos y no C.

Tipos de funciones

Hay dos grandes cambios respecto a los tipos de funciones:

En las versiones anteriores ya contábamos con métodos dependientes, pero ahora tenemos la posibilidad de convertir dicho método en una función, lo cual es común en Scala, pero era imposible para los métodos cuyo tipo de retorno era una ruta dependiente del tipo de entrada. Ahora podemos hacer esto:

def fooMethod(a: A): a.Foo = a.key

val fooFunction: (a: A) => a.Foo = fooMethod

La segunda característica más importante, son los tipos de funciones implícitas. Por ejemplo:

type MyFunction[B] = implicit A => B

Al igual que en otros escenarios con implicaciones, esto significa que si se encuentra un valor implícito de tipo A en el ámbito, se pasará; si no hay una A implícita en el ámbito, debe pasarse explícitamente o la compilación fallará.

Así que dado este método método:

def foo(f: Foo)(implicit a: A): B = ???

Podemos reescribirlo de la siguiente forma:

type MyFunction = implicit A => B

def foo(f: Foo): MyFunction

Esto nos permite definir foo cómo un valor de función. Sin tipos de funciones implícitas, solo podemos definirlo como un método, ya que en Scala 2 tener parámetros implícitos en un método automáticamente significa que no se puede expresar como una función.

Descubre la formación a tu medida
Rellena el formulario para obtener más información sobre los cursos.
Tamaño de la empresa *
Términos y condiciones *

Generando tuplas

Las tuplas ya no se implementan a través de los traits de TupleN que terminan (bastante arbitrariamente) en Tuple22. En su lugar, se implementan de manera similar a las Listas, con su estructura recursiva, lo que significa que básicamente se están convirtiendo en HLists sin forma.

Aunque esto significa que el límite de 22 se ha ido, esa no es la principal ventaja. La mejora principal se da en la forma en que tratamos las tuplas en sí mismas, porque las tuplas anidadas ahora pueden tratarse como planas; (a, b, c) serán exactamente las mismas que (a, (b, (c, ()))). Esto permite una buena programación genérica similar a lo que podemos hacer con HLists, como mapear sobre ellas con funciones monomórficas o incluso polimórficas.

Tipos opacos

Es similar a un tipo alias, pero en lugar de ser un alias solo para el programador, también es un alias para el compilador, lo que significa que el compilador realmente diferencia los dos.

type Nombre = String

Esto nos da la posibilidad de usar "Nombre" como un tipo, lo que puede hacer que el código sea más comprensible. Sin embargo, nada nos impide pasar realmente una cadena "normal" donde se requiere un nombre. Peor aún, si tenemos otro alias de tipo, por ejemplo: Apellido = String, nada nos impide pasar accidentalmente un Apellido donde se espera Nombre y viceversa, ya que desde el punto de vista del compilador, todos son solo Strings.

Si queremos lograr el comportamiento en el que el compilador evite dicho uso, debemos recurrir a clases de valor. Esto significa escribir algo como esto:

class FirstName(val underlying: String) extends AnyVal

class LastName(val underlying: String) extends AnyVal

Esto es un poco repetitivo y tiene un leve impacto en el rendimiento. Si añadimos la extensión AnyVal, ella se encarga de parte de esa penalización de rendimiento, pero no en todos los casos de uso y además nuestro código queda un poco feo..

Hay una biblioteca que permite definirlos de una manera un poco más elegante:

@newtype case class FirstName(underlying: String)

pero es una sintaxis bastante repetitiva. Además, estos nuevos tipos deben definirse en un objeto u objeto de paquete.

Scala 3 ha introducido nuevos tipos opacos.

opaque type FirstName = String

El concepto newType y opaque type con prácticamente el mismo. Nombre es un tipo en sí mismo, y pasar un valor de tipo Apellido donde se espera que Nombre no se compile. Puede haber cierta resistencia a introducir una nueva palabra clave, en cuyo caso la propuesta es usar: nuevo tipo Nombre = String.

Tipo lambdas

Scala 3 obtiene soporte de lenguaje completo para lambdas sin tener que recurrir a feeds o bibliotecas externas.

Supongamos que necesitamos F [_], pero queremos pasar un constructor que necesita dos tipos (como Mapa o O), o en otras palabras, queremos pasar (﹡ → ﹡) → ﹡ donde se necesita ﹡ → ﹡.

En lugar de tener que definir un lambda como este:

({ type T[A] = Map[Int, A] })#T

Lo haremos de una forma más simple:

[A] => Map[Int, A]

Los parámetros de tipo lambdas soportan variaciones y límites, por ejemplo:

[+A, B <: C] => Whatever[A, B, C]

Parámetros borrados

Hay situaciones en las que solo necesitamos algunos parámetros en la firma, por ejemplo para la evidencia en restricciones de tipo generalizadas, y nunca se utilizan en el propio cuerpo. Se sigue generando un código innecesario para dichos parámetros, que se puede evitar con la palabra clave "erased".

Por ejemplo:

def foo[S, T](s: S, t: T)(implicit ev: S =:= T)

Se usa como:

def foo[S, T](s: S, t: T)(implicit erased ev: S =:= T)

Por lo tanto, siempre que tengamos uno o más parámetros que se usen solo para la verificación de tipos, el uso de la palabra clave "erased" hará que el código sea más eficaz.

Enumeraciones

Una de las construcciones más torpes de Scala tiene un rediseño completo, lo que significa reemplazar el código de esta manera:

1object WeekDay extends Enumeration { 2 3type WeekDay = Value 4 5val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value 6 7}

Con este código:

1enum WeekDay { 2 3case Mon, Tue, Wed, Thu, Fri, Sat, Sun 4 5}

Pueden contener miembros personalizados y algunos métodos prácticos ya predefinidos.

Ejemplo:

1enum Weekday(val index: Int) { 2 3private def next(i: Int) = (i + 1) % 7 4 5private def prev(i: Int) = (i + 7) - 1 % 7 6 7def nextDay = Weekday.enumValue(next(index)) 8 9def prevDay = Weekday.enumValue(prev(index)) 10 11case Mon   extends Weekday(0) 12 13case Tue   extends Weekday(1) 14 15... 16 17}

Igualdad multiversal

El compilador ahora coincidirá con los tipos al comparar valores y fallará en la compilación si hay una falta de coincidencia.

Ya podemos modelar algo como “foo” == 123 mediante clases (en realidad se realiza en varias bibliotecas), pero con Scala 3 obtenemos el soporte de idioma nativo.

Restricción de conversiones implícitas

El compilador ahora requerirá una importación de características de idioma no solo cuando se define una conversión implícita sino también cuando se aplica.

Seguridad nula

Esto se basa en la función de tipos de unión, que nos permite definir el tipo de resultado de operaciones que pueden generar una excepción de puntero nulo (útil para la interoperabilidad conJava) como Foo | nulo.

Aprende Scala

Tal y cómo hemos comentado al inicio de la entrada, si deseas seguir aprendiendo este lenguaje de programación, puedes consultar nuestro curso de Scala donde encontrarás numerosos recursos que te permitirán impulsar tu carrera. Además, si deseas introducirte en este mundo puedes consultar nuestro tutorial de primeros pasos con Scala o nuestro tutorial más avanzado sobre el Uso de las Clases Case y Pattern Matching en Scala.

Descubre la formación a tu medida
Rellena el formulario para obtener más información sobre los cursos.
Tamaño de la empresa *
Términos y condiciones *
Tutoriales relacionados
Data Class de Kotlin: ¿Qué es?
Descubre Qué es una Data Class de Kotlin y Cómo Crearla: Guía Completa de las Data Class de Kotlin para Principiantes y Desarrolladores Experimentados
Primeros pasos con Kotlin: Tutorial para principiantes
Domina Kotlin desde cero con nuestro tutorial exclusivo: ¡Descubre los secretos de la programación Android y crea apps increíbles! 🚀
Manipulación de archivos de Excel con C#
Guía Completa para Aprender a Manipular todo tipo de Archivos de Excel con C-Sharp (C#): Descubre Cómo Mejorar tus Conocimientos en C#11
Manipulación de Strings en C# | Tutorial completo
Domina la magia de las cadenas en C#: aprende trucos poderosos de manipulación de strings y desbloquea el potencial de tu código. ¡Descúbrelo ahora!
Tabla de contenido
Todas las novedades en Scala
Intersecciones y uniones
Parámetros Trait
Tipos de funciones
Generando tuplas
Tipos opacos
Tipo lambdas
Parámetros borrados
Enumeraciones
Igualdad multiversal
Restricción de conversiones implícitas
Seguridad nula
Aprende Scala
Descubre la formación a tu medida
Rellena el formulario para obtener más información sobre los cursos.
Tamaño de la empresa *
Términos y condiciones *