Tipos algebraicos en Java 21

Miguel Rafael Esteban Martín (LogicaAlternativa.com)

Disclaimer/Aviso

La idea de la charla e incluso el abstract de la charla se pensó antes que...


Java Language update By Brian Goetz ( Devoxx Belgium 2023)

https://youtu.be/TIHx6MNt79Y?si=myDt3CERSwZeIjsm

Tipos Algebraicos

El nombre de "Algebraico" proviene de las matemáticas discretas.

Se define estructuras de manera recursiva, indicando los distintos casos de definición y un axioma de clausura (formulando lo que queda fuera de lo definido)

En esencia son combinación de otros tipos, es decir, no son tipos de datos atómicos.

  • Tipos producto. Asociados con AND. Se asocian con Tuplas y Records.
  • Tipos suma. Asociados con OR. Enumerados y también Sealed Class/Interfaces.

Tipo producto

Los "operadores del producto" son los tipos de sus campos.

Los valores que pueden contener es el producto cartesiano de todos los valores de sus campos. Se representan con tuplas.

Se puede establecer una equivalencia con el concepto de "conjunción lógica" (AND).

El caso más sencillo es la tupla (tipo par) A x B que contiene todas las parejas de elementos del tipo A y del tipo B. El orden es importante A x B ≠ B x A

In [1]:
class A {
    boolean type1;    
    int type2;
}

Tipo suma

Se puede establecer una equivalencia con el concepto de "unión disjunta" (OR).

Un tipo que puede tomar diferentes valores pero estos son fijos.

Una de las ventajas es que permite comprobar los valores en tiempo de compilación.

Los enumerados son un caso especial de tipo suma(constructores sin argumentos).

In [2]:
enum TrafficLight {
    GREEN, YELLOW, RED;
}

Pattern matching

Los valores de los tipos algebraicos son analizados con Pattern Matching (Concordancia de patrones) para identificar el tipo de dato y extraer sus valores. A menudo este análisis es recursivo.

Esta es una característica clave para aprovechar los tipos de datos algebraicos realizando verificaciones en tiempo de compilación.

Tipos algebraicos en Java

Records en Java

Los records son tuplas (tipos producto), simplemente los valores son accedidos por "nombre". En Java todo es semántico y se accede por nombre y además es menos error prone.

En Java desde la versión 16, (funcionalidad preliminar en java 14) (JEP 395) . Es el soporte que da Java a los tipos productos. Son inmutables y tienen predefinidos los métodos getters, equals, hasCode y toString

No es equivalente a Lombock, tiene un carácter semántico en Java.

In [3]:
record MyRecord(boolean type1, int type2){}

Sealed clases/interfaces

En java desde la versión 17 (preliminar en Java 15). (JEP 409)

También tienen un carácter semántico.

Son los tipos suma, enumerados con esteroides.

In [4]:
sealed interface MyError{
     
  int getCode();

  final class IOHttpCodeError extends RuntimeException implements MyError {
    public int getCode() { return 1; }
    public String getSource() { return "HTTP"; }
  }
  final class RageValidationError extends RuntimeException implements MyError {
    public int getCode() { return 2; }
    public String getType() { return "Range"; }
  }
}

Pattern Matching en Java

Se potencia el comando switch con Sealed class/interfaces se lanzó con Java 21, (JEP 441) (versión preliminar en Java 17).

El Record Pattern (JEP 440) se lanzó también con java 21.

Realmente esto cambia el juego y hace el sistema de tipos de Java mucho más robusto.

In [5]:
class ExamplePatterMatching {
    public static String getError( MyError error ){
        return switch( error ) {
            case MyError.IOHttpCodeError ioe -> 
                "IO MyError code: %d, source: %s"
                    .formatted(ioe.getCode(), ioe.getSource());
            case MyError.RageValidationError ve -> 
                "Validation error code:%d, type:%s"
                    .formatted(ve.getCode(), ve.getType());
        };
    }
}

En resumen

  • Tanto los records como los sealed class/interfaces tienen un carácter semántico y es la nueva representación del concepto de Data en Java.

  • Con ellos el sistema de tipos de Java es más rico y robusto.

  • El uso de Pattern Matching, con el remoledado comando switch, permite explotar de manera eficiente los tipos algebraicos, permitiendo:

    • Comparar, convertir al tipo adecuado y extraer sus valores.
    • Ser exhaustivo en comprobar esta información.
    • Limitar el ámbito de las variables asignadas de los datos obtenidos.

Ejemplos de uso

¿Qué vamos a ver?

  • DSLs Domain Specific Language
  • Estructuras de datos. "El api de Immutable Collection que Java no se atrevió hacer"
  • Side Effects:
    • "El Optional que Java no quiso hacer"
    • "La gestión de errores que estaría genial que Java lo tuviera"

Creación de DSLs

Domain Specific Language Crear nuestros propios lenguajes para un dominio específico.

En este caso es un lenguaje interpretado donde existe unos "comandos o instrucciones" y uno o varios interpretes, y donde:

  • La fáse de análisis sintáctica, la dará el propio compilador del lenguaje Java.
  • La fase semántica y/o la ejecución, la dará un interprete.
  • Es un tipo de DSL que es abstracción independiente de la "tecnología".

Estructuras de datos

El api de Immutable Collection que Java no se atrevió hacer

Efectos de lado

"El Optional que Java no quiso hacer"

Efectos de lado

"La gestión de errores que estaría genial que Java lo tuviera"

¿Preguntas?

Muchas gracias.