---
marp: true
title: Una mirada funcional a Java 22
theme: gaia
footer: #commitconf 2024
author: Antonio Muñoz
color: #fff
transition: fade-out
backgroundImage: url('https://tonivade.es/commitconf24/images/background.png')
---

# Una mirada :eyes: funcional a Java :two::two:

<!-- _backgroundImage: url('https://tonivade.es/commitconf24/images/frontpage.png') -->

_Antonio Muñoz_

---

# ¿Quien soy?

* Programo en Java desde Java 1.1.
* Actualmente trabajo en https://clarity.ai como backender.
* Mi primera charla en una conferencia.
* Me encontraréis en:
    - Mastodon: @tonivade@jvm.social
    - Github: https://github.com/tonivade

<!-- por favor, sed benevolentes con mis errores-->

---

# Encuesta

* Java 22? :sunglasses:
* Java 21? :thumbsup:
* Java 17? :ok:
* Java 11? :warning:
* Todavía Java 8? :cry:
* Y anteriores a Java 8? :exploding_head:

<!-- preguntar a la audiencia qué version de Java usan en pro -->

---

# Jetbrains ecosystem survey

* [¿Qué versión de Java usáis regularmente?](https://www.jetbrains.com/lp/devecosystem-2023/java/#java_versions)
* El 11% usan Java 20.
* El 45% usan Java 17.
* El 38% usan Java 11.
* Todavía el 50% usan Java 8.

<!--
  Java 20 no es LTS
  Java 21 todavía no aparece en las respuestas porque cuando se realizó
    la encuesta no se había lanzado todavía
  Todavía Java 8 es el más usado -->

---

# Java :eight: :older_man:

* Lanzado en Marzo 2014 :rocket:
* No existía ni tiktok :notes: ni openai :robot:
* Ese mismo año se lanzó spring-boot v1.0.
* Docker y kubernetes daban sus primeros pasos :baby:

<!--
    Rajoy Presidente
    JuanCar I abdicó en Felipe VI
    Obama
    Papa Francisco I desde 2013
    -->

---

# Agenda :calendar:

* El largo camino a Java 22.
* Tipos de datos algebraicos.
* Ejemplos.
* Futuro.

---

# Java Release Cadence :coffee:

* Dos release al año.
* LTS.
* Preview features.
* They have a plan.

<!--
    en Marzo y Septiembre
    en los últimos 10 años han ido introduciendo una serie de cambios que como resultado han dado a poder modelar e implementar ADTs.
    probablemente en java 23 la preview de string interpolation se eliminará.
    ha recibido un feedback muy negativo y se están planteando rediseñar completamente la nueva funcionalidad.
-->

---

# Switch expressions :railway_track:

```java
var value = switch (input) {
    case "a" -> 1;
    case "b" -> 2;
    default -> 0;
};
```

* Incluido en Java 14.
* Una nueva vida para `switch`.
* Expresión.
* `yield`.

<!--
  se puede:
   asignar a una variable
   devolver en un return
   usarlo en una lambda
   anidar un switch dentro de un case de otro switch
  eso significa que tiene que ser exhaustivo -->

---

# yield

```java
var value = switch (input) {
    case "a" -> {
        yield 1;
    }
    case "b" -> {
        yield 2;
    }
    default -> {
        yield 0;
    }
};
```

---

# Records :video_camera:

```java
public record Movie(String title, int year, int duration) {
}
```

* Incluido en Java 16.
* Muy esperado por la comunidad.
* Inmutables.
* Constructor canónico.

<!-- no son un remplazo para los POJOs tradicionales
  son contenedores de datos inmutables
  NO USAR COMO Entidades de Hibernate -->

---

# Records: Constructor Canónico :building_construction:

```java
public record Movie(String title, int year, int duration) {
    public Movie {
        if (title == null || title.isEmpty()) {
            throw new IllegalArgumentException();
        }
    }
}
```

* Se ejecuta **siempre**.

<!--
    incluso durante la deserialización.
    en un POJO normal, al deserializar el objeto no se llama al constructor nunca,
    los atributos del objeto se rellenan desde fuera de manera insegura
    si tuviéramos una validación en el constructor se la saltaría
  -->

---

# Records: Constructor Canónico :building_construction:

```java
public record Movie(String title, List<String> cast) {
    public Movie {
        cast = List.copyOf(cast);
    }
}
```

* Copia defensiva para evitar modificar el contenido del record.

<!-- alguien desde fuera podría modificar la lista
 o si la lista contuviera otros objetos que no son
 inmutables -->

---

# Sealed classes and interfaces :closed_lock_with_key:

```java
public sealed interface Shape {
    record Square(int side) implements Shape {}
    record Rectangle(int weight, int height) implements Shape {}
    record Circle(int radius) implements Shape {}
}
```

* Incluido en Java 17.
* Jerarquías de clases cerradas.
* `non-sealed`

---

# non-sealed

```java
public sealed interface Shape {
    record Square(int side) implements Shape {}
    record Rectangle(int weight, int height) implements Shape {}
    record Circle(int radius) implements Shape {}
    non-sealed interface CustomShape extends Shape {}
}
```

---

# Pattern matching for switch

```java
var result = switch (obj) {
    case Integer i -> String.format("int %d", i);
    case Long l    -> String.format("long %d", l);
    case Double d  -> String.format("double %f", d);
    case String s  -> String.format("String %s", s);
    default        -> obj.toString();
};
```

* Incluido en Java 21.

<!-- todavía no soporta primitivos -->

---

# Null patterns

```java
var result = switch (obj) {
    case null      -> "null";
    case Integer i -> String.format("int %d", i);
    case Long l    -> String.format("long %d", l);
    case Double d  -> String.format("double %f", d);
    case String s  -> String.format("String %s", s);
    default        -> obj.toString();
};
```

<!-- antes no era posible usarlo -->

---

# Record patterns

```java
var area = switch (this) {
    case Square(var side) -> side * side;
    case Rectangle(var weight, var height) -> weight * height;
    case Circle(var radius) -> Math.PI * Math.pow(radius, 2);
};
```

* Incluido en Java 21.
* Deconstructores, nos permite acceder a los componentes de los objectos.
* Exhaustiveness.

<!-- antes la regla era siempre añadir un default en los switch
  para no llevarnos sorpresas, pero ahora se convierte en un
  antipatrón. Debemos dejar trabajar al compilador y que verifique si
  el switch es exhaustivo y si modificamos la definición de Shape
  añadiendo nuevos tipos, el compilador fallará en este switch para
  indicar que nos falta cubrir un caso.
   -->

---

# Guarded patterns

```java
var result = switch (point) {
    case Point(var x, var y) when y == 0 -> processX(x);
    case Point(var x, var y) -> processXY(x, y);
};
```

* Podemos añadir condiciones adicionales usando `when`

---

# Nested patterns

```java
var result = switch (this) {
    case Square(Point(var x, var y), var side) -> ...;
    case Rectangle(Point(var x, var y), var weight, var height) -> ...;
    case Circle(Point(var x, var y), var radius) -> ...;
};
```

* Podemos anidar patrones

---

# Unnamed variables and patterns

```java
var result = switch (obj) {
    case Integer _ -> "int";
    case Long _    -> "long";
    case Double _  -> "double";
    case String _  -> "string";
    default        -> "other";
};
```

* Incluido en Java 22.
* Mejora para el pattern matching.
* Eliminar verbosidad.

---

# Tipos de datos algebraicos

* AKA ADTs (algebraic data types).
* Viene de las matemáticas.
* Recursivo.
* Productos y sumas de tipos.
    `a + b` `a * b`
* Tienen propiedades algebraicas.

<!--
  las mismas propiedades que aprendimos en la escuela primaria
  no voy a entrar en más detalle en esto simplemente para que
  sepáis que tiene una base matemática -->

---

# Otros lenguajes con soporte ADTs

* JVM:
    * Scala, Kotlin
* Rust
* C#
* TypeScript
* Otros lenguajes funcionales:
    * Haskell, F#, OCaml

---

# ADTs

* ¿Cómo podemos representarlos en Java?
* Un `record` es un producto de tipos.
* Un `sealed interface` es una suma de tipos.
* ¿Pero eso para qué me sirve?
  * Estructuras de datos.
  * Estructuras de control.
  * DSLs
  * Manejo de errores.

---

# ADTs: List

```java
sealed interface List<T> {
    record NonEmpty<T>(T head, List<T> tail) implements List<T> {}
    record Empty<T>() implements List<T> {}
}
```

<!-- No estoy diciendo que usemos este tipo de dato tal cual
  es solo un ejemplo -->

---

# ADTs: List (map)

```java
sealed interface List<T> {
    default <R> List<R> map(Function<T, R> mapper) {
        return switch (this) {
            case NonEmpty<T>(var head, var tail)
                -> new NonEmpty<>(mapper.apply(head), tail.map(mapper));
            case Empty<T> _ -> new Empty<>();
        };
    }
}
```

<!-- la implementación es recursiva
  por lo que si la lista es muy larga tendremos un
  StackOverflowException -->

---

# ADTs: List (filter)

```java
sealed interface List<T> {
    default List<T> filter(Predicate<T> filter) {
        return switch (this) {
            case NonEmpty<T>(var head, var tail) when filter.test(head)
                -> new NonEmpty<>(head, tail.filter(filter));
            case NonEmpty<T>(var head, var tail)
                -> tail.filter(filter);
            case Empty<T> _ -> new Empty<>();
        };
    }
}
```

---

# ADTs: List (fold)

```java
sealed interface List<T> {
    default T fold(T initial, BinaryOperator<T> operator) {
        return switch (this) {
            case NonEmpty<T>(var head, var tail)
                -> tail.fold(operator.apply(initial, head), operator);
            case Empty<T> _ -> initial;
        };
    }
}
```

---

# ADTs: Tree

```java
sealed interface Tree<T> {
    record Node<T>(T value, Tree<T> left, Tree<T> right) implements Tree<T> { }
    record Leaf<T>(T value) implements Tree<T> {}
}
```

---

# ADTs: Optional

```java
sealed interface Optional<T> {
    record Empty<T>() implements Optional<T> { }
    record Present<T>(T value) implements Optional<T> {}
}
```

---

# ADTs: Optional (map)

```java
sealed interface Optional<T> {
    default <R> Optional<R> map(Function<T, R> mapper) {
        return switch (this) {
            case Present<T>(var value) -> new Present<>(mapper.apply(value));
            case Empty<T> _ -> new Empty<>();
        };
    }
}
```

---

# ADTs: Optional (filter)

```java
sealed interface Optional<T> {
    default Optional<T> filter(Predicate<T> filter) {
        return switch (this) {
            case Present<T>(var value) when filter.test(value) -> this;
            case Present<T> _ -> new Empty<>();
            case Empty<T> _ -> new Empty<>();
        };
    }
}
```

---

# ADTs: Optional (fold)

```java
sealed interface Optional<T> {
    default <R> R fold(Supplier<R> onEmpty, Function<T, R> onPresent) {
        return switch (this) {
            case Present<T>(var value) -> onPresent.apply(value);
            case Empty<T> _ -> onEmpty.get();
        };
    }
}
```

---

# ADTs: Either

```java
sealed interface Either<L, R> {
    record Left<L, R>(L left) implements Either<L, R> { }
    record Right<L, R>(R right) implements Either<L, R> {}
}
```

<!-- podemos implementar map, fold y otros muchos métodos -->

---

# ADTs: DSLs

```java
sealed interface Expr {
    record Val(int value) implements Expr {}
    record Sum(Expr left, Expr right) implements Expr {}
    record Diff(Expr left, Expr right) implements Expr {}
    record Times(Expr left, Expr right) implements Expr {}
}
```

<!-- es un DSL muy simple para definir expresiones matemáticas -->

---

# ADTs: DSLs

```java
sealed interface Expr {
    default int evaluate() {
        return switch (this) {
            case Val(var value) -> value;
            case Sum(var left, var right) -> left.evaluate() + right.evaluate();
            case Diff(var left, var right) -> left.evaluate() - right.evaluate();
            case Times(var left, var right) -> left.evaluate() * right.evaluate();
        };
    }
}
```

<!-- usando pattern matching podemos implementar la evaluación de la expresión -->

---

# ADTs: DSLs

```java
sealed interface Expr {
    default String asString() {
        return switch (this) {
            case Val(var value) -> String.valueOf(value);
            case Sum(var left, var right) -> "(" + left.asString() + "+" + right.asString() + ")";
            case Diff(var left, var right) -> "(" + left.asString() + "-" + right.asString() + ")";
            case Times(var left, var right) -> "(" + left.asString() + "*" + right.asString() + ")";
        };
    }
}
```

<!-- y generar un string que describa la expresión -->

---

# ADTs: DSLs

```java
sealed interface Expr {
    default void print() {
        System.out.println(asString() + "=" + evaluate());
    }
}
```

---

# ADTs: DSLs

```java
static void main() {
    sum(val(1), val(2)).print();
    times(diff(val(10), val(8)), val(2)).print();
}
```

```
(1+2)=3
((10-8)*2)=4
```

---

# ADTs: Json

```java
sealed interface Json {
    enum JsonNull implements Json { NULL }
    enum JsonBoolean implements Json { TRUE, FALSE }
    record JsonString(String value) implements Json {}
    record JsonNumber(Number value) implements Json {}
    record JsonObject(Map<String, Json> value) implements Json {}
    record JsonArray(List<Json> value) implements Json {}
}
```

---

# ADTs: Json

```java
sealed interface Json {
    default String asString() {
        return switch (this) {
            case JsonNull _ -> "null";
            case JsonBoolean b -> switch (b) {
                case TRUE -> "true";
                case FALSE -> "false";
            };
            ...
        };
    }
}
```

---

# ADTs: Json

```java
sealed interface Json {
    default String asString() {
        return switch (this) {
            ...
            case JsonString(var value) -> "\"" + value + "\"";
            case JsonNumber(var value) -> String.valueOf(value);
            ...
        };
    }
}
```

---

# ADTs: Json

```java
sealed interface Json {
    default String asString() {
        return switch (this) {
            ...
            case JsonObject(var map)
                -> map.entrySet().stream()
                    .map(entry ->  "\"" + entry.getKey() + "\":" + entry.getValue().asString())
                    .collect(joining(",", "{", "}"));
            ...
        };
    }
}
```

---

# ADTs: Json

```java
sealed interface Json {
    default String asString() {
        return switch (this) {
            ...
            case JsonArray(var array)
                -> array.stream()
                    .map(Json::asString)
                    .collect(joining(",", "[", "]"));
        };
    }
}
```

---

# ADTs: Json

```java
static void main() {
    var json = array(
        object(
            entry("name", string("Toni")),
            entry("age", number(46)),
            entry("old", JsonBoolean.TRUE)),
        object(
            entry("name", string("Baby")),
            entry("age", JsonNull.NULL),
            entry("old", JsonBoolean.FALSE))
    );
    System.out.println(json.asString());
}
```

---

# ADTs: Json

```json
[{"old":true,"name":"Toni","age":46},{"old":false,"name":"Baby","age":null}]
```

<!-- en OOP tradicional habríamos necesitado sobre escribir el método asString
 en cada subclase o utilizar el patron visitor.
 Pattern Matching prácticamente jubila el patrón visitor.
 Es otra manera de hacer Polimorfismo, usando pattern matching
 -->

---

# ADTs: Errores

```java
interface MovieRepository {
    MovieResponse create(Movie movie);
}

sealed interface MovieResponse permits MovieCreated, MovieError {}

record MovieCreated(UUID id) implements MovieResponse {}
```

<!-- imaginemos que tenemos una aplicación para gestionar
  nuestras películas preferidas -->

---

# ADTs: Errores

```java
sealed interface MovieError extends MovieResponse {
    record DuplicatedMovie(UUID id) implements MovieError {}
    record InvalidDuration(int duration) implements MovieError {}
    record InvalidYear(int year) implements MovieError {}
    record InvalidStars(int stats) implements MovieError {}
    record EmptyTitle() implements MovieError {}
    record EmptyDirector() implements MovieError {}
    record EmptyCast() implements MovieError {}
    record DuplicatedActor(String actor) implements MovieError {}
}
```

* Definir errores de dominio.

---
# ADTs: Errores

```java
@PostMapping("/movies")
public ResponseEntity<UUID> create(@RequestBody Movie movie) {
    var result = repository.create(movie);
    return switch (result) {
        case MovieCreated(var id) -> ResponseEntity.ok(id);
        case MovieError e -> ResponseEntity.of(toProblem(e)).build();
    };
}
```

<!-- en el controlador aplicamos pattern matching para devolver
     el identificador generado,
     o un error describiendo con todo el detalle posible qué ha ocurrido -->

---
# ADTs: Errores

```java
static ProblemDetail toProblem(CreateMovieResponse.MovieError error) {
    var detail = switch (error) {
      case DuplicatedMovie(int id) -> "duplicated movie with id: " + id;
      case InvalidDuration(var duration) -> "invalid duration: " + duration;
      case InvalidYear(var duration) -> "invalid year: " + duration;
      case InvalidStars(var stars) -> "invalid stars: " + stars;
      case EmptyTitle() -> "title cannot be empty";
      case EmptyDirector() -> "director cannot be empty";
      case EmptyCast() -> "cast cannot be empty";
      case DuplicatedActor(var actor) -> "duplicated actor: " + actor;
    };
    return ProblemDetail.forStatusAndDetail(BAD_REQUEST, detail);
}
```

<!--
    aquí uso una novedad de spring-boot 3.x que son los ProblemDetail
    en caso de ser necesario podría devolver un status code diferente
    -->

---

# ADTs: Either

```java
interface MovieRepository {
    Either<MovieError, UUID> create(Movie movie);
}
```

<!--
    usualmente esto evoluciona al uso de Either
    ya que tiene muchas más posibilidades
    tienes dos canales uno para errores (Left) y otro para el resultado <Right>
    Either en si también es una suma de tipos, porque puede ser o Left o Right
    -->

```java
@PostMapping("/movies")
public ResponseEntity<UUID> create(@RequestBody Movie movie) {
    var result = repository.create(movie);
    return result.fold(
        error -> ResponseEntity.of(toProblem(error)).build(),
        ResponseEntity::ok);
}
```

<!-- en el controlador podemos usar el método fold para construir la respuesta -->

---

# Próximamente :watch:

<!-- _class: lead -->

---

# Primitive types in patterns

```java
var result = switch (obj) {
    case int i     -> String.format("int %d", i);
    case long l    -> String.format("long %d", l);
    case double d  -> String.format("double %f", d);
    case String s  -> String.format("String %s", s);
    default        -> obj.toString();
};
```

* Preview en Java 23 (sep 2024).

<!-- si tenéis sdkman instalado podéis instalar una version EA
 de java 23 que ya incluye esta funcionalidad -->

---

# Primitive types in patterns

```java
jshell> int i = 0;
i ==> 0
jshell> i instanceof byte b
$2 ==> true
```

```java
jshell> int i = 123213;
i ==> 123213
jshell> i instanceof byte b
$4 ==> false
```

* Castings seguros

---

# Bola de cristal :crystal_ball:

<!-- _class: lead -->

<!-- podemos saltarnos esta parte si vamos mal de tiempo -->

---

# Derived Record Creation

```java
Point newPoint = oldPoint with {
    x *= 2;
    y *= 2;
};
```

* Draft.
* AKA withers.

---

# Static patterns

```java
var result = switch (optional) {
    case Optional.of(var value) -> value.toString();
    case Optional.empty() -> "empty";
};
```

* Early work.
* AKA deconstructors.
* Mejora para pattern matching.
* Cualquier clase.

---

# Constant patterns

```java
var result = switch (optional) {
    case Point(0, var y) -> process(y);
    case Point(var x, var y) -> process(x, y);
};
```

---

# ¿Qué falta todavía? 🤓

* Tail recursion.
* Soporte de tipos de datos primitivos en genéricos.

<!-- los ADT son recursivos por lo que muchos algoritmos se implementan usando recursividad
  y java todavía no es un lenguaje que se pueda considerar stack safe
  aunque en java 23 será posible usar tipos primitivos en pattern matching, todavía no esta
  soportado para tipos genéricos -->

---

# ¿Preguntas?

<!-- _class: lead -->

---

# ¡Gracias! :sparkling_heart:

<!-- _class: lead -->

---

# JEPs

- [Switch expressions](https://openjdk.org/jeps/361) 14
- [Records](https://openjdk.org/jeps/395) 16
- [Sealed Classes and interfaces](https://openjdk.org/jeps/409) 17
- [Pattern matching for switch](https://openjdk.org/jeps/441) 21
- [Record patterns](https://openjdk.org/jeps/440) 21
- [Unnamed variables and patterns](https://openjdk.org/jeps/456) 22
- [Primitive types in patterns](https://openjdk.org/jeps/455) 23 (1st preview)
- [Derived record creation](https://openjdk.org/jeps/468) Candidate

<!-- recomiendo leerlos ya que son muy instructivos y están muy bien redactados -->

---

# Documentación Oficial

- [Switch Expressions](https://docs.oracle.com/en/java/javase/21/language/switch-expressions.html)
- [Record Classes](https://docs.oracle.com/en/java/javase/21/language/records.html)
- [Sealed Classes](https://docs.oracle.com/en/java/javase/21/language/sealed-classes-and-interfaces.html)
- [Pattern Matching](https://docs.oracle.com/en/java/javase/21/language/pattern-matching.html)
- [Unnamed variables and patterns](https://docs.oracle.com/en/java/javase/22/language/unnamed-variables-and-patterns.html)

---

# Artículos / Videos

- [Data Oriented Programming](https://www.infoq.com/articles/data-oriented-programming-java/) Brian Goetz
- [Data Oriented Programming with Java 21](https://www.youtube.com/watch?v=8FRU_aGY4mY) Nicolai Parlog
- [Java 23: Restoring the Balance with Primitive Patterns](https://nipafx.dev/inside-java-newscast-66/) Nicolai Parlog
- [Why ADTs are important?](https://www.youtube.com/watch?v=LkqTLJK2API) Bartosz Milewski

<!-- TODO:

  why use pattern matching?
    - reduce cognitive load.
    - pattern exhaustiveness.

   meta:
   - target audience
   - manage expectations
   - content
   - confirm expectations
   - links to follow
    -->