diff --git a/slides.md b/slides.md index 47b6655..2f7901b 100644 --- a/slides.md +++ b/slides.md @@ -386,6 +386,14 @@ } ``` +```java +// NonEmpty[head=1, tail=NonEmpty[head=2, tail=NonEmpty[head=3, tail=Empty[]]]] +List list = new List.NonEmpty<>( + 1, new List.NonEmpty<>( + 2, new List.NonEmpty<>( + 3, new List.Empty<>()))); +``` + @@ -443,6 +451,11 @@ } ``` +```java +var list = list(1, list(2, list(3, empty()))); +var result = list.fold(0, Integer::sum); // 6 +``` + --- # ADTs: Tree @@ -511,6 +524,12 @@ } ``` +```java +optional.fold(() -> "unknown", Object::toString); +// usando java.util.Optional +optional.map(Object::toString).orElseGet(() -> "unknown"); +``` + --- # ADTs: Either @@ -522,7 +541,12 @@ } ``` - +* Una manera alternativa para la gestión de errores. +* Y otra manera de definir una suma de tipos + + --- @@ -836,6 +860,18 @@ --- +# ¿Qué falta todavía? 🤓 + +* Tail recursion. +* Soporte de tipos de datos primitivos en genéricos. + + + +--- + # Próximamente :watch: @@ -930,19 +966,7 @@ --- -# ¿Qué falta todavía? 🤓 - -* Tail recursion. -* Soporte de tipos de datos primitivos en genéricos. - - - ---- - -# ¿Preguntas? +# ¿Preguntas? :thinking: @@ -988,6 +1012,7 @@ - [Why ADTs are important?](https://www.youtube.com/watch?v=LkqTLJK2API) Bartosz Milewski --- + # Enlaces - Slides HTML: https://tonivade.es/commitconf24/slides.html diff --git a/slides_en.md b/slides_en.md new file mode 100644 index 0000000..883137e --- /dev/null +++ b/slides_en.md @@ -0,0 +1,987 @@ +--- +marp: true +title: A functional look to 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') +--- + +# A functional look :eyes: to Java :two::two: + + + +_Antonio Muñoz_ + +--- + +# Who am I? + +* Java programer since v1.1. +* Working as a backender in https://clarity.ai +* I'm at: + - Mastodon: @tonivade@jvm.social + - Github: https://github.com/tonivade + - Email: me@tonivade.es + + + +--- + +# Agenda :calendar: + +* The long way to Java 22. +* Algebraic data types. +* Examples. +* Futuro. + +--- + +# Java Release Cadence :coffee: + +* Two releases per year. +* LTS. +* Preview features. +* They have a plan. + + + +--- + +# Switch expressions :railway_track: + +```java +var value = switch (input) { + case "a" -> 1; + case "b" -> 2; + default -> 0; +}; +``` + +* Included in Java 14. +* A new life for `switch`. +* Expression. +* `yield`. + + + +--- + +# 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) { +} +``` + +* Included in Java 16. +* Long awaited functionality by the Java community. +* Immutables. +* Canonic constructor. + + + +--- + +# Records: Canonic Constructor :building_construction: + +```java +public record Movie(String title, int year, int duration) { + public Movie { + if (title == null || title.isEmpty()) { + throw new IllegalArgumentException(); + } + } +} +``` + +* Is executed **always** + + + +--- + +# Records: Canonical Constructor :building_construction: + +```java +public record Movie(String title, List cast) { + public Movie { + cast = List.copyOf(cast); + } +} +``` + +* Defensive copy of the collection + + + +--- + +# 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 {} +} +``` + +* Included in Java 17. +* Closed hierarchies of classes. +* `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(); +}; +``` + +* Included in Java 21. + + + +--- + +# 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(); +}; +``` + + + +--- + +# 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); +}; +``` + +* Included in Java 21. +* Deconstructors, it allows to access to the components of the records. +* Exhaustiveness. + + + +--- + +# 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); +}; +``` + +* We can add additional conditions using `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) -> ...; +}; +``` + +* We can nest pattern matching + +--- + +# Unnamed variables and patterns + +```java +var result = switch (obj) { + case Integer _ -> "int"; + case Long _ -> "long"; + case Double _ -> "double"; + case String _ -> "string"; + default -> "other"; +}; +``` + +* Included in Java 22. +* An improvement to pattern matching. +* Reduce verbosity. + +--- + +# Algebraic Data Types + +* AKA ADTs +* Mathematical bases. +* Recursive. +* Products and sums of tipos. + `a + b` `a * b` +* They have algebraic properties. + + + +--- + +# Other languages with ADTs support + +* JVM: + * Scala, Kotlin +* Rust +* C# +* TypeScript +* Otros lenguajes funcionales: + * Haskell, F#, OCaml + +--- + +# ADTs + +* How we can represent ADTs in Java? +* A `record` is a product type. +* A `sealed interface` is a sum type. +* Can be used to implement: + * Data Structures. + * Control Structures. + * DSLs. + * Domain errors. + +--- + +# ADTs: List + +```java +sealed interface List { + record NonEmpty(T head, List tail) implements List {} + record Empty() implements List {} +} +``` + +```java +// NonEmpty[head=1, tail=NonEmpty[head=2, tail=NonEmpty[head=3, tail=Empty[]]]] +List list = new List.NonEmpty<>( + 1, new List.NonEmpty<>( + 2, new List.NonEmpty<>( + 3, new List.Empty<>()))); +``` + + + +--- + +# ADTs: List (map) + +```java +sealed interface List { + default List map(Function mapper) { + return switch (this) { + case NonEmpty(var head, var tail) + -> new NonEmpty<>(mapper.apply(head), tail.map(mapper)); + case Empty _ -> new Empty<>(); + }; + } +} +``` + + + +--- + +# ADTs: List (filter) + +```java +sealed interface List { + default List filter(Predicate filter) { + return switch (this) { + case NonEmpty(var head, var tail) when filter.test(head) + -> new NonEmpty<>(head, tail.filter(filter)); + case NonEmpty(var head, var tail) + -> tail.filter(filter); + case Empty _ -> new Empty<>(); + }; + } +} +``` + +--- + +# ADTs: List (fold) + +```java +sealed interface List { + default T fold(T initial, BinaryOperator operator) { + return switch (this) { + case NonEmpty(var head, var tail) + -> tail.fold(operator.apply(initial, head), operator); + case Empty _ -> initial; + }; + } +} +``` + +```java +var list = list(1, list(2, list(3, empty()))); +var result = list.fold(0, Integer::sum); // 6 +``` + +--- + +# ADTs: Tree + +```java +sealed interface Tree { + record Node(T value, Tree left, Tree right) implements Tree { } + record Leaf(T value) implements Tree {} +} +``` + +--- + +# ADTs: Optional + +```java +sealed interface Optional { + record Empty() implements Optional { } + record Present(T value) implements Optional {} +} +``` + +--- + +# ADTs: Optional (map) + +```java +sealed interface Optional { + default Optional map(Function mapper) { + return switch (this) { + case Present(var value) -> new Present<>(mapper.apply(value)); + case Empty _ -> new Empty<>(); + }; + } +} +``` + +--- + +# ADTs: Optional (filter) + +```java +sealed interface Optional { + default Optional filter(Predicate filter) { + return switch (this) { + case Present(var value) when filter.test(value) -> this; + case Present _ -> new Empty<>(); + case Empty _ -> new Empty<>(); + }; + } +} +``` + +--- + +# ADTs: Optional (fold) + +```java +sealed interface Optional { + default R fold(Supplier onEmpty, Function onPresent) { + return switch (this) { + case Present(var value) -> onPresent.apply(value); + case Empty _ -> onEmpty.get(); + }; + } +} +``` + +```java +optional.fold(() -> "unknown", Object::toString); +// usando java.util.Optional +optional.map(Object::toString).orElseGet(() -> "unknown"); +``` + +--- + +# ADTs: Either + +```java +sealed interface Either { + record Left(L left) implements Either { } + record Right(R right) implements Either {} +} +``` + +* An alternative way to manage errors. +* Another way to define a sum types. + + + +--- + +# 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 {} +} +``` + + + +--- + +# 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(); + }; + } +} +``` + + + +--- + +# 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() + ")"; + }; + } +} +``` + + + +--- + +# 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 value) implements Json {} + record JsonArray(List 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}] +``` + + + +--- + +# ADTs: Errores + +```java +interface MovieRepository { + MovieResponse create(Movie movie); +} + +sealed interface MovieResponse permits MovieCreated, MovieError {} + +record MovieCreated(UUID id) implements MovieResponse {} +``` + + + +--- + +# 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 {} +} +``` + +* Define all possible domain errors + +--- +# ADTs: Errores + +```java +@PostMapping("/movies") +public ResponseEntity 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(); + }; +} +``` + + + +--- +# 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); +} +``` + + + +--- + +# ADTs: Either + +```java +interface MovieRepository { + Either create(Movie movie); +} +``` + + + +```java +@PostMapping("/movies") +public ResponseEntity create(@RequestBody Movie movie) { + var result = repository.create(movie); + return result.fold( + error -> ResponseEntity.of(toProblem(error)).build(), + ResponseEntity::ok); +} +``` + + + +--- + +# What is missing yet? 🤓 + +* Tail recursion. +* Full support for primitive types in generics and pattern matching. + + + +--- + +# Próximamente :watch: + + + +--- + +# 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(); +}; +``` + +* First Preview in Java 23 (sep 2024). + + + +--- + +# 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 +``` + +* Safe castings + +--- + +# Bola de cristal :crystal_ball: + + + + + +--- + +# 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. +* An improvement to pattern matching. +* Use any class. + +--- + +# Constant patterns + +```java +var result = switch (optional) { + case Point(0, var y) -> process(y); + case Point(var x, var y) -> process(x, y); +}; +``` + +--- + +# Questions? :thinking: + + + +--- + +# Thank you! :sparkling_heart: + + + +--- + +# 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 + + + +--- + +# Oficial Documentation + +- [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) + +--- + +# Articles / 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 +- [Java Language Update 2023](https://www.youtube.com/watch?v=TIHx6MNt79Y) Brian Goetz +- [Why ADTs are important?](https://www.youtube.com/watch?v=LkqTLJK2API) Bartosz Milewski + +--- + +# Enlaces + +- Slides HTML: https://tonivade.es/commitconf24/slides.html +- Slides PDF: https://tonivade.es/commitconf24/slides.pdf + + \ No newline at end of file diff --git a/snippets/List.java b/snippets/List.java index 2a5f2df..c0e0995 100644 --- a/snippets/List.java +++ b/snippets/List.java @@ -32,4 +32,38 @@ case Empty _ -> initial; }; } + + default List prepend(T element) { + return new NonEmpty<>(element, this); + } + + default List append(T element) { + return switch (this) { + case NonEmpty(var head, var tail) + -> new NonEmpty<>(head, tail.append(element)); + case Empty _ -> new NonEmpty<>(element, this); + }; + } + + static List of(T...values) { + List list = empty(); + for (T value : values) { + list = list.append(value); + } + return list; + } + + static List list(T head, List tail) { + return new NonEmpty(head, tail); + } + + static List empty() { + return new Empty(); + } + + static void main() { + var list = list(1, list(2, list(3, empty()))); + var result = list.fold(0, Integer::sum); + System.out.println(result); + } }