Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Value Types et Pattern Matching : Remettre les ...

José
April 18, 2024

Value Types et Pattern Matching : Remettre les données au centre des applications

La modélisation de données en Java est l'un des aspects du langage qui a très peu évolué depuis 15 ans. Les choses changent, puisque Valhalla commence à publier des éléments intéressants. La version LW5 apporte des classes primitives et des value classes. Le projet Amber remet les données au centre des applications : les records permettent de meilleures modélisations, et le Pattern Matching ainsi que les Types Scellés permettent de mieux écrire les traitements. Dans le futur, Valhalla permettra d'unifier les types objet et les types primitifs et de gérer différemment les valeurs nulles. Amber va continuer de développer le pattern matching avec les déconstructeurs pour les classes classiques et les patterns nommés. Ces nouvelles approches vont apporter de nouvelles façons d'organiser les applications, de mieux distribuer le code en modules indépendants, de créer des données sous de nouvelles formes et d'avoir de meilleures performances pour nos traitements en mémoire.

José

April 18, 2024
Tweet

More Decks by José

Other Decks in Programming

Transcript

  1. Value Types et Pattern Matching Remettre les données au centre

    des applications José Paumard Java Developer Advocate Java Platform Group Rémi Forax Maître de conferences Université Gustave Eiffel
  2. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 3

    Tune in! Inside Java Newscast JEP Café Dev.java Inside.java Inside Java Podcast Sip of Java Cracking the Java coding interview
  3. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 5

    https://openjdk.org/ OpenJDK is the place where it all happens
  4. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 6

    https://jdk.java.net/ OpenJDK is the place where it all happens
  5. https://twitter.com/He bah non! https://github.com/forax https://speakerdeck.com/forax OpenJDK, ASM, Tatoo, Pro, etc…

    One of the Father of invokedynamic (Java 7) Lambda (Java 8), Module (Java 9) Constant dynamic (Java 11) Record, text blocks, sealed types (Java 14 / 15) Valhalla (Java 19+)
  6. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 9

    Data Oriented Programming Amber Java on modern CPU Valhalla + Lilliput Java in the cloud Leyden + Crac + Galahad Foreign functions API (C) + Vector API Panama Modernizing Java
  7. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | Confidential:

    Internal/Restricted/Highly Restricted 10 Amber
  8. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 12

    Encapsulation OOP according to Java class City { private String name; public String toString() { ... } }
  9. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 13

    Interface and sub-typing OOP according to Java class City implements Named { ... } Named named = new City(...);
  10. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 14

    Late binding (virtual polymorphism) OOP according to Java class City { public String toString() { ... } } Named named = new City(...); var name = named.toString(); // calls City.toString()
  11. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 15

    Anatomy of a Web Application Browser request request response Your code! JSON API DB API Authentication API SUCCESS STORY! REST API
  12. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 16

    OOP in Java APIs (interfaces) are more important than code
  13. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 17

    DOP in Java Data first! Data are more important that code When the data changes, the compiler helps
  14. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 18

    Model the problem using a class and an interface + late binding Object Oriented Programming final class City implements Named { private final String name; private final int population; public String name() { return name; } } final class Department implements Named { private final String name; public String name() { return name; } } interface Named { public String name(); }
  15. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 19

    Encapsulation  defensive copy in constructors OOP + Defensive Copy final class Department implements Named { private final String name; private final List<City> cities; public Department(String name, List<City> cities) { this.name = Objects.requireNonNull(name); this.cities = List.copyOf(cities); } }
  16. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 20

    Separate Data and Code Data Oriented Programming interface Named { } final class City implements Named { } final class Department implements Named { } static String name(Named named) { if (named instanceof City) { var city = (City) named; if (city.population() < 0) { throw new ISE("danger danger"); } return city.name(); } if (named instanceof Department) { var department = (Department) named; return department.name(); } throw new AssertionError(); }
  17. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 22

    Data is more important than code - Not always true Compiler helps (like with OOP) - Records are data definition - Sealed types make switch exhaustive - Record patterns detect structural modification Data Oriented Programming
  18. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 23

    Polymorphism - Add new subtypes - No new operations Wadler’s Expression of the Problem Pattern Matching - Add new operations - No new subtypes You cannot get both  If you are not the owner of the code:
  19. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 24

    To avoid the creation of bindings Or relax constraints Also works on local variables, for, try-with-resources, … Does not work on method declaration (APIs must have a name) More Patterns: Unnamed Pattern case City(String _, int population) -> ...; case Department(String name, _) -> ...;
  20. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 25

    Deconstructor enables record pattern on class More Patterns: Record Pattern on Classes class Point { private int x, y; matcher(int x, int y) Point { // provisional syntax match this.x, this.y; } }
  21. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 26

    Allows matcher methods to be named Named Pattern final class Optional<T> { final private T value; final private boolean present; matcher(T t) of { // provisional syntax if (present) match this.value; no-match; } matcher() empty { // provisional syntax if (present) match; no-match; } } Optional<String> opt = ...; switch(opt) { case Optional.of(String s) -> ...; case Optional.empty() -> }
  22. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 27

    Using record pattern in assignments Imperative Destructuring Point p = ...; let Point(int x, int y) = p; for (let Map.Entry(var key, var value): entrySet) { ... }
  23. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | Confidential:

    Internal/Restricted/Highly Restricted 29 Valhalla
  24. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 32

    Why does nobody follow this principle? Valhalla
  25. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 33

    Summing populations Primitive Types or Objects? int[] populations = {...}; int totalPopulation = 0; for (int population: populations) { totalPopulation += population; } int totalPopulation = Arrays.stream(populations).sum(); record Population(int population) {} Population[] populations = {...}; Population total = 0; for (Population pop: populations) { total = total.add(pop); } Population total = Arrays.stream(populations) .reduce( Population.zero(), Population::add);
  26. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 34

    Creating histograms Primitive Types or Objects? // this is the histogram of population by city Map<String, Integer> populationByCity = ...; record City(String name, Population population) {} record Population(int population) {} Map<City, Population> populationByCity = ...;
  27. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 35

    1st goal: make it so that you do not have to choose between readable code and performances Make abstraction (almost) free Codes like a class, Works like an int Valhalla
  28. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 36

    The performances of both patterns should be the same Valhalla: goals int[] populations = {...}; int totalPopulation = 0; for (int population: populations) { totalPopulation += population; } record Population(int population) {} Population[] populations = {...}; Population total = 0; for (Population pop: populations) { total = total.add(pop); }
  29. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 37

    Abstraction for free (on stack) - No allocation of intermediate objects Improved information density (on heap) - No header - Use immediate value (no pointer) Valhalla: goals
  30. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 38

    Temporary objects should be free - Optional<T> - True builders - Logger.warning() .onConsole("Hello Devoxx!"); Wrapped values should be free - Integer, Complex, Month, LocalDate, etc… Abstraction for Free (on Stack)
  31. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 39

    Integer[] array = new Integer[4]; Improve Information Density (on Heap) header header = 32 + 64 bits (heap < 32GB) 64 + 64 bits header header header header
  32. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 40

    High level goal: realign Java with the current hardware Valhalla is an OpenJDK project started in 2014 by Brian Goetz and John Rose - August 2014: first meeting - 2017 – 2022: MVT, LW1, LW2, LW3, LW4 (prototypes) - 202?: LW5, Value classes as a Preview feature - 20??: Universal Specialized Generics Valhalla in detail
  33. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 41

    Value types in LW5 LW5 is still under construction VM almost ready Compiler not ready But we have a bytecode rewriter!
  34. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 42

    Summing the populations of Cities Primitives vs. Objects int total = 0; for (City city: cities) { total += city.population(); } record City(String name, int population) {} City[] cities = ...;
  35. Copyright © 2021, Oracle and/or its affiliates | 43 Extra

    pointers / space in memory Layout in Memory cities String int Primitive version
  36. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 44

    Summing the populations of Cities Primitives vs. Objects int total = 0; for (City city: cities) { total += city.population(); } record Population(int population) {} record City(String name, Population population) {} Population total = new Population(0); for (Population pop: populations) { total = total.add(pop); } City[] cities = ...; record City(String name, int population) {} City[] cities = ...;
  37. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 45

    Extra pointers / space in memory Layout in Memory cities Population int String int cities String population Primitive version Object version
  38. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 46

    Extra allocations / indirections Instructions on Stack int total = 0; for (City city: cities) { total += city.population(); } record Population(int population) {} record City(String name, Population population) {} Population total = new Population(0); for (Population pop: populations) { total = new Population( total.population + pop.population); } City[] cities = ...; record City(String name, int population) {} City[] cities = ...;
  39. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 47

    Two different effects: - Operations on stack (extra instructions) - Memory layout (extra indirections) Can be solved separately: - Escape analysis - Soving memory layout involves tradeoffs Primitive vs. Objects
  40. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 48

    A class that gives up its identity Instances have no address in memory But still: - Inherits from java.lang.Object - Implements interfaces - Defines members (methods, fields, nested classes, …) - Values are references / instances of the class (or null) What is a Value Class?
  41. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 49

    Declaration Scalarization: on the stack, the JVM replaces an instance by its field values (if not null) What is a Value Class? public value class Population { final int population; }
  42. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 50

    On the stack declaring Is equivalent to: What is a Value Class? Population population = new Population(23); int population = 23;
  43. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 51

    Problem with method calls - Method calls should pass an address…  the fields have to be unmodifiable - All fields are declared final - But that’s not enough! Value Classes are Non-Modifiable
  44. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 52

    Final fields can be modified during the execution of a constructor  the constructor is transformed into a chain of static methods, each call creates a new value class instance - All fields are declared final - But that’s not enough! Constructor Issue
  45. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 53

    Value Class Constructor value class Population { final int population; Population(int pop) { this.population = pop; } } value class Population { final int population; static Population<new>(int value) { var tmp1 = aconst_init Population var tmp2 = withfield Population.value(tmp1, value) return tmp2 } } Generated by javac
  46. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 54

    Operations that requires an address or values from the header need a different semantics == recursively calls == on the fields System.identityHashCode() calls Objects.hash() on all fields Value Class Semantics
  47. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 55

    Operations that requires an address or values from the header need a different semantics synchronized throws IllegalMonitorStateException new WeakRef(value) throws IndentityException Value Class Semantics
  48. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 56

    Compounds with existing issues: Bad CPU branch prediction VM deoptimization GC read barrier (Shenandoah and ZGC) Don’t use == in Object.equals()! value class Population { public boolean equals(Object other) { if (other == this) return true: // ahhhhh ... } }
  49. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 57

    Use Pattern Matching instead value class Population { final int pop; public boolean equals(Object o) { return o instanceof Population other && other.pop == this.pop; } … }
  50. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 59

    V1: City is a regular record, Population is an int V2: City and Population are regular records Bench: Population of Cities with Regular Records record City(String name, int population) {} record City(String name, Population population) {} record Population(int amount) {}
  51. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 60

    Bench: Population of Cities with Regular Records City[] cities = ...; int sum = 0; for (City city: cities) { sum += city.population(); } Benchmark V1 Score Error Units sum populations 24,985 ± 0,097 us/op Population total = Population.zero(); for (City city: cities) { total = total.add(city.population()); } Benchmark V2 Score Error Units sum populations 68,069 ± 0,686 us/op City[] cities = ...; int sum = 0; for (City city: cities) { sum += city.population().amount(); } Benchmark V2 Score Error Units sum populations 33,335 ± 0,140 us/op
  52. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 61

    V3: City is a regular record, Population is a value record Bench: Population of Cities with Value Records record City(String name, Population population) {} record Population(int amount) {}
  53. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 62

    Bench: Population of Cities with Value Records City[] cities = ...; int sum = 0; for (City city: cities) { sum += city.population(); } (same as previous) Benchmark Score Error Units sum populations 24,985 ± 0,097 us/op Population total = Population.zero(); for (City city: cities) { total = total.add(city.population()); } Benchmark V3 Score Error Units sum populations 35,012 ± 0,128 us/op City[] cities = ...; int sum = 0; for (City city: cities) { sum += city.population().amount(); } Benchmark V3 Score Error Units sum populations 34,954 ± 0,309 us/op
  54. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 63

    Fields cannot be flattened easily: - we cannot represent null (what should be the default value?) - concurrency issue if the CPU does several stores / loads for on field The Field of a Value Class (or Array)? class City { final Population population; }
  55. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 64

    Allows to declare that a type does not allow null proposed syntax: Population! the VM can throw a NPE if null is assigned Only need for fields and array elements Add Emotion on Type
  56. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 65

    Users should not be able to define the default values Java is not C++ !  All fields are zeroed (false, 0, 0.0, etc…)  but this breaks encapsulation Non Null Default Value Classes
  57. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 66

    Add a default empty constructor  enables a non null value class Value Class with a Default Instance value class Population { // proposed syntax final int value; default Population(); // default empty constructor Population(int value) { this.value = value; } }
  58. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 67

    Use the ! (emotion, bang) character Only valid if Population is a value class with a default instance Defining a Non-Null Element value record City(String name, Population! population) {} var populationArray = new Population![10]; List<Population!> populationList = new ArrayList<>();
  59. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 68

    V4: Population is a value record with a default instance, City declares it as a non-null field Bench: Value Class with Default Instance record Population(int amount) { default population(); } record City(String name, Population! population) {}
  60. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 69

    Bench: with Value Class, Defaut Instance, Non Null City[] cities = ...; int sum = 0; for (City city: cities) { sum += city.population(); } (same as previous) Benchmark Score Error Units sum populations 24,985 ± 0,097 us/op Population! total = Population.zero(); for (City city: cities) { total = total.add(city.population()); } Benchmark V4 Score Error Units sum populations 25,343 ± 0,028 us/op City[] cities = ...; int sum = 0; for (City city: cities) { sum += city.population().amount(); } Benchmark V4 Score Error Units sum populations 25,230 ± 0,045 us/op
  61. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 70

    Array of Pointers Layout in Memory Population int cities String population City[] Object version
  62. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 71

    Array of Pointers of Population Struct Layout in Memory cities String Population City[] / Population! version
  63. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 72

    Array of Struct Layout in Memory cities String Population City![] / Population! version String Population String Population String Population
  64. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 73

    Bench: with Value Class, Defaut Instance, Non Null City![] cities = ...; int sum = 0; for (City city: cities) { sum += city.population(); } (same as previous) Benchmark Score Error Units sum populations 24,985 ± 0,097 us/op Population! total = Population.zero(); for (City! city: cities) { total = total.add(city.population()); } Benchmark Score Error Units sum populations 11,087 ± 0,005 us/op City![] cities = ...; int sum = 0; for (City! city: cities) { sum += city.population().amount(); } Benchmark Score Error Units sum populations 11,098 ± 0,011 us/op
  65. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 75

    Do we Have a Concurrency Issue? value record City(int population, int surface) {}
  66. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 76

    Do we Have a Concurrency Issue? class Holder { private City! city = new City(1, 1); public static void main(String[] args) { Holder holder = new Holder(); new Thread( () -> { for(;;) holder.city = new City(1, 1); } ).start(); new Thread( () -> { for(;;) holder.city = new City(2, 2); } ).start(); ... } }
  67. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 77

    Do we Have a Concurrency Issue? for(;;) { City city = holder.city; if (city.population != city.surface) { throw new AssertionError("" + city); } }
  68. @Holder 5/5/2023 Copyright © 2021, Oracle and/or its affiliates |

    78 Do we Have a Concurrency Issue? population surface 1 2 1, 1 2, 2
  69. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 79

    Easy to solve, do not use City! Cheap Solution! class Holder { private City city = new City(1, 1); public static void main(String[] args) { ... } }
  70. @City @Holder 5/5/2023 Copyright © 2021, Oracle and/or its affiliates

    | 80 Cheap Solution! city 1, 1 2, 2 population surface 1 2
  71. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 81

    Make value classes always atomic by default We need a non-atomic flag (not yet implemented in the JVM) Non-atomic non-atomic value record Population(int amount) { // proposed syntax default Population(); // default empty constructor }
  72. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 82

    Valhalla + Amber? What if there is an interface? What if there is a switch on types?
  73. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 83

    Hierarchy on Named Elements sealed interface Named permits City, Department, Region {} record Population(int amount) {} record City(String name, Population population) implements Named {} record Department(List<City> cities, String name) implements Named {} record Region(List<Department> department, String name) implements Named {}
  74. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 84

    Minimum Name Length static String name(Named named) { return switch(named) { case City city -> city.name(); case Department department -> department.name(); case Region region -> region.name(); }; } static Named min(Named n1, Named n2) { if (name(n1).compareTo(name(n2) < 0) { return n1; } return n2; }
  75. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 85

    Switch not yet fully optimized Switch + value: bimodal perf? OOP vs. Switch / Identity Value OOP + Records Switch + Records OOP + Value Records Switch + Value Records Score Error Units 320,742 ± 1,122 ns/op 434,220 ± 1,543 ns/op Score Error Units 248,244 ± 1,489 ns/op 424,537 ± 35,296 ns/op
  76. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 86

    Aaaaahh, a lot of boxing!  we need specialized generics Bench: default instance value class Score Error Units 320,742 ± 1,122 ns/op 434,220 ± 1,543 ns/op Score Error Units 248,244 ± 1,489 ns/op 424,537 ± 35,296 ns/op OOP + Records Switch + Records OOP + Value Records Switch + Value Records Score Error Units 1666,613 ± 638,825 ns/op 2167,923 ± 455,078 ns/op OOP + Default Value Records Switch + Default Value Records
  77. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 88

    Generalize and allow ! and ? on identity classes too Is equivalent to: Emotion Type on Identity Classes? void foo(String! s) { ... } void foo(String s) { Objects.requireNonNull(s); ... } And also the inverse so the default is ! ? means nullable See JSpecify.org
  78. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 89

    All the value based classes of the JDK are retrofitted to be value classes - Optional, LocalDateTime, are value classes - Booean, Integer, Double, etc… are non-atomic value classes with a default instance Existing Classes Retrofited
  79. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 90

    Non null default instance Value class No pointer, great, until boxing  Generics needs to be specialized (but backward compatible) ArrayList<Population!> is flattened <N extends Named> N min(N n1, N n2) is JITed several times Specialized Generics
  80. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 91

    Bridge the gap: - Allow method calls (equals() toString() hashCode()) on primitive literals - true.toString() - 2.equals(3) // parsing error 2. is a double - Allow ArrayList<int> - int is a subtype of Integer for overriding - Automatic conversions between int[] and Integer![] Primitive and Value Classes
  81. 5/5/2023 Copyright © 2021, Oracle and/or its affiliates | 93

    What You are Giving Up, What You Get Identity Class Value Class Non-Null Value Class with a Default Instance Non-Null Non-Atomic Value Class with a Default Instance Address in memory Null + encapsulation of default value Atomicity of load / store Scalarization on the stack Flattening of small classes Flattening of medium classes