Slide 1

Slide 1 text

objectcomputing.com © 2018, Object Computing, Inc. (OCI). All rights reserved. No part of these notes may be reproduced, stored in a retrieval system, or transmitted, in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise, without the prior, written permission of Object Computing, Inc. (OCI) Groovy 5 Update Presented by Dr Paul King Unity Foundation & VP Apache Groovy © 2023 Unity Foundation. All rights reserved. unityfoundation.io Halifax, Nova Scotia, October 7-10, 2023

Slide 2

Slide 2 text

Dr Paul King Unity Foundation Groovy Lead V.P. Apache Groovy Author: https://www.manning.com/books/groovy-in-action-second-edition Slides: https://speakerdeck.com/paulk/groovy-roadmap Examples repo: https://github.com/paulk-asert/upcoming-groovy/ Twitter/X | Mastodon: @paulk_asert | @[email protected]

Slide 3

Slide 3 text

Groovy • Multi-faceted programming language • Static and dynamic natures • Strengths: • Extensibility including runtime and compile-time metaprogramming • Java-like syntax and integration • Domain Specific Language support • Scripting

Slide 4

Slide 4 text

Groovy: How it started If Java/the JDK is your chocolate cake… Groovy is the Icing! Quote circa 2007 Java Groovy

Slide 5

Slide 5 text

Groovy: How it started If Java/the JDK is your chocolate cake… Groovy is the Icing! Quote circa 2007 More cake… More Icing! 2023 Java Groovy Groovy: How it’s going

Slide 6

Slide 6 text

Groovy status in 2023: Healthy • Twenty years (and a few weeks) since the first commit. Happy Birthday Groovy! • > 600K lines of source code • > 40K commits • > 10K issues & enhancements resolved • > 500 contributors • > 240 releases • > 3B downloads and still accelerating (currently approx. 2.5-3 million downloads per day)

Slide 7

Slide 7 text

Parrot Parser • Lambdas • Method references • New operators & Safe index ===, !===, ?=, !in, !instanceof • Additional Java compatibility GDK enhancements • average(), takeBetween(), shuffle(), … AST transforms • @NullCheck • @Groovydoc Libraries/Tooling • Split package remediation • Java module system improvements • JSR-308 Annotation improvements • YAML builder/slurper Groovy 3 - Summary

Slide 8

Slide 8 text

Consolidation & Structuring • Maven coordinates • Module changes • Indy only, Parrot only • ~33% smaller zip • ~10% smaller core jar Language Features • Switch expressions • Records & Sealed types • Language integrated query • Improved type annotations Libraries/Tooling • Built-in type checkers • Built-in macro methods • TOML builder/slurper • JavaShell • Improved ranges AST transforms • @POJO • @RecordType • Groovy Contracts GDK enhancements Groovy 4 - Summary

Slide 9

Slide 9 text

Language features: switch expressions class CustomIsCase { boolean isCase(subject) { subject > 20 } } assert switch(10) { case 0 -> false case '11' -> false case null -> false case 0..9 -> false case 1, 2 -> false case [9, 11, 13] -> false case Float -> false case { it % 3 == 0 } -> false case new CustomIsCase() -> false case ~/\d\d/ -> true default -> false }

Slide 10

Slide 10 text

Switch expression: differences to Java Java Groovy JDK versions 14+ (12/13 preview) 8+ “ -> ” syntax (switch rule)   “ : … yield” syntax   Supported case selector expressions Constant • Boolean, Number, String Enum constant Constant • Boolean, Number, String, null Enum constant List expression Range expression Closure Regex Pattern Class expression Enhanced switch (17-19 preview) Type pattern Guard/when Null Class or Closure Closure already supported Extensible via isCase  

Slide 11

Slide 11 text

Records • Shorthand for data classes (immutable DTOs) • Produces a class that: • Is implicitly final • Has a private final field for each property, e.g. color • Has an accessor method for each property of the same name, e.g. color() • Has a default Point(int, int, String) constructor • Has a default serialVersionUID of 0L • Has implicit toString(), equals() and hashCode() methods record Point(int x, int y, String color) { } def blueOrigin = new Point(0, 0, 'Blue') Incubating

Slide 12

Slide 12 text

@RecordType Alternative style for: @RecordType class Cyclist { String firstName String lastName } def richie = new Cyclist('Richie', 'Porte') Incubating record Cyclist(String firstName, String lastName) { }

Slide 13

Slide 13 text

Records: Groovy enhancements record Material(String name, int strength, String warranty, String color) { } def straw = new Material('Straw', 4, 'light winds only', 'yellow') assert straw.toList() == ['Straw', 4, 'light winds only', 'yellow'] assert straw[1] == 4 assert straw.size() == 4 Incubating

Slide 14

Slide 14 text

Records: Groovy enhancements record Material(String name, int strength, String warranty, String color) { } def straw = new Material('Straw', 4, 'light winds only', 'yellow') assert straw.toList() == ['Straw', 4, 'light winds only', 'yellow'] assert straw[1] == 4 assert straw.size() == 4 Incubating

Slide 15

Slide 15 text

Records: Groovy enhancements record Material(String name, int strength, String warranty, String color) { } def straw = new Material('Straw', 4, 'light winds only', 'yellow') assert straw.toList() == ['Straw', 4, 'light winds only', 'yellow'] assert straw[1] == 4 assert straw.size() == 4 def sticks = new Material(name: 'Sticks', strength: 42, warranty: 'light winds', color: 'brown') assert sticks.toMap() == [name: 'Sticks', strength: 42, warranty: 'light winds', color: 'brown'] assert sticks[3] == 'brown' Incubating

Slide 16

Slide 16 text

Records: Groovy enhancements @RecordOptions(copyWith = true, components = true) record Material(String name, int strength, String warranty, String color) { } def straw = new Material('Straw', 4, 'light winds only', 'yellow') assert straw.toList() == ['Straw', 4, 'light winds only', 'yellow'] assert straw[1] == 4 assert straw.size() == 4 def sticks = new Material(name: 'Sticks', strength: 42, warranty: 'light winds', color: 'brown') assert sticks.toMap() == [name: 'Sticks', strength: 42, warranty: 'light winds', color: 'brown'] assert sticks[3] == 'brown' def bricks = sticks.copyWith(name: 'Bricks', color: 'red', warranty: 'heavy winds') assert bricks.components() instanceof Tuple4 assert bricks.components().toList() == ['Bricks', 42, 'heavy winds', 'red'] Incubating

Slide 17

Slide 17 text

Native vs emulated Records @RecordOptions(mode=RecordTypeMode.AUTO) // default, not needed record Person(String name, int age) {} @RecordOptions(mode=RecordTypeMode.NATIVE) record Point(int x, int y, String color) { } @RecordOptions(mode=RecordTypeMode.EMULATE) @RecordType class Cyclist { String firstName String lastName } Regardless of keyword or annotation style, native or emulated records can be selected: • AUTO – native if JDK16+ • NATIVE – record info in bytecode, error if JDK < 16 • EMULATE – record info captured as annotations (works on JDK8+ but won’t be seen as records by Java)

Slide 18

Slide 18 text

Declarative Record Customization def a = new Agenda(topics: ['Sealed', 'Records']) assert a.topics().size() == 2 assert a.toString() == 'Agenda[topics=[Sealed, Records]]' a.topics().clear() a.topics() << 'Switch Expressions' assert a.topics().size() == 1 record Agenda(List topics) { }

Slide 19

Slide 19 text

Declarative Record Customization def a = new Agenda(topics: ['Sealed', 'Records']) assert a.topics().size() == 2 shouldFail(UnsupportedOperationException) { a.topics().clear() } assert a.toString() == 'Agenda([Sealed, Records])' @ToString @PropertyOptions(propertyHandler = ImmutablePropertyHandler) record Agenda(List topics) { }

Slide 20

Slide 20 text

Using records with AST Transforms: @Memoized @Builder record Point(int x, int y, String color) { @Memoized String description() { "${color.toUpperCase()} point at ($x,$y)" } } record Developer(Integer id, String first, String last, String email, List skills) { @Builder Developer(Integer id, String full, String email, List skills) { this(id, full.split(' ')[0], full.split(' ')[1], email, skills) } }

Slide 21

Slide 21 text

Using records with AST Transforms: @Requires @Sortable @Requires({ color && !color.blank }) record Point(int x, int y, String color) { } @Sortable record Point(int x, int y, String color) { } var points = [ new Point(0, 100, 'red'), new Point(10, 10, 'blue'), new Point(100, 0, 'green'), ] println points.toSorted(Point.comparatorByX()) println points.toSorted(Point.comparatorByY()) println points.toSorted(Point.comparatorByColor())

Slide 22

Slide 22 text

record Quadratic(double a, double b = 0, double c = 0) { @Newify(Complex) List solve() { var discriminant = Complex(b * b - 4 * a * c) findRoots(Complex(-b), discriminant, Complex(2 * a)) } @OperatorRename(div = 'divide', plus = 'add', minus = 'subtract') static List findRoots(Complex negB, Complex discriminant, Complex twoA) { var sqrtDiscriminant = discriminant.sqrt() var root1 = (negB + sqrtDiscriminant) / twoA var root2 = (negB - sqrtDiscriminant) / twoA [root1, root2] } } Using records with AST Transforms: @Newify @OperatorRename • Representing a quadratic equation: ax2 + bx + c

Slide 23

Slide 23 text

assert [ new Quadratic(2.0, -4.0, 2.0), new Quadratic(2.0, -4.0), new Quadratic(1.0), new Quadratic(a:2.0, b:-5.0, c:-3.0) ]*.solve()*.toSet()*.toString() == [ '[(1.0, 0.0)]', '[(2.0, 0.0), (0.0, 0.0)]', '[(0.0, 0.0)]', '[(3.0, 0.0), (-0.5, 0.0)]' ] Using records with other features: Named/Default params Default parameters Named parameters

Slide 24

Slide 24 text

Records: differences to Java Java Record Groovy Emulated Record Groovy Native Record JDK version 16+ 8+ 16+ Serialization Record spec Traditional Record spec Recognized by Java, Groovy Groovy Java, Groovy Standard features • accessors • tuple constructor • toString, equals, hashCode    Optional enhancements  toMap, toList, size, getAt, components, copyWith, named-arg constructor Customisable via coding    Customisable via AST transforms (declarative)   

Slide 25

Slide 25 text

Record-like functionality: summary Accessors toString() equals() hashCode() Constructor Groovy version JDK version Standard Groovy JavaBean From Object From Object Named-arg 1.0+ 6+ @Immutable JavaBean From properties From properties Tuple and named-arg 1.7+ 6+ @ToString - From properties - - 1.8+ 6+ @EqualsAnd HashCode - - From properties - 1.8+ 6+ @Canonical JavaBean From properties From properties Tuple and named-arg 1.8+ 6+ Emulated & native records Record From components From components Tuple and named-arg 4.0+ 8+ & 16+

Slide 26

Slide 26 text

Sealed Types – Good for ADTs @Sealed interface Tree {} @Singleton final class Empty implements Tree { String toString() { 'Empty' } } @Canonical final class Node implements Tree { T value Tree left, right } Tree tree = new Node<>(42, new Node<>(0, Empty.instance, Empty.instance), Empty.instance) assert tree.toString() == 'Node(42, Node(0, Empty, Empty), Empty)'

Slide 27

Slide 27 text

Sealed Types – Hybrid hierarchies sealed class Shape permits Circle, Polygon, Rectangle { } final class Circle extends Shape { } non-sealed class Polygon extends Shape { } final class Pentagon extends Polygon { } sealed class Rectangle extends Shape permits Square { } final class Square extends Rectangle { }

Slide 28

Slide 28 text

sealed class Shape permits Circle, Polygon, Rectangle { } final class Circle extends Shape { } non-sealed class Polygon extends Shape { } final class Pentagon extends Polygon { } sealed class Rectangle extends Shape permits Square { } final class Square extends Rectangle { } Sealed Types – Hybrid hierarchies Shape Circle Polygon Rectangle Pentagon Square

Slide 29

Slide 29 text

Sealed Types – Hybrid hierarchies • Groovy follows Scala style of non- sealed being optional • We envisage a future CodeNarc rule which could enforce the Java style sealed class Shape permits Circle, Polygon, Rectangle { } final class Circle extends Shape { } non-sealed class Polygon extends Shape { } final class Pentagon extends Polygon { } sealed class Rectangle extends Shape permits Square { } final class Square extends Rectangle { }

Slide 30

Slide 30 text

Sealed types: differences to Java Java Sealed Type Groovy Emulated Sealed Type Groovy Native Sealed Type JDK version 17+ 8+ 17+ Recognized by Java, Groovy Groovy Java, Groovy “non-sealed” to reopen Required Optional Optional

Slide 31

Slide 31 text

Groovy 4 Other features: extensible type checker import groovy.transform.TypeChecked @TypeChecked(extensions = 'groovy.typecheckers.RegexChecker') def whenIs2020Over() { def newYearsEve = '2023-12-31' def matcher = newYearsEve =~ /(\d{4})-(\d{1,2})-(\d{1,2}/ } def newYearsEve = '2023-12-31' def matcher = newYearsEve =~ /(\d{4})-(\d{1,2})-(\d{1,2}/ // PatternSyntaxException

Slide 32

Slide 32 text

Groovy 4 Other features: language integrated queries from p in persons leftjoin c in cities on p.city.name == c.name where c.name == 'Shanghai' select p.name, c.name as cityName from p in persons groupby p.gender having p.gender == 'Male' select p.gender, max(p.age) from p in persons orderby p.age in desc, p.name select p.name from n in numbers where n > 0 && n <= 3 select n * 2 from n1 in nums1 innerjoin n2 in nums2 on n1 == n2 select n1 + 1, n2 • Projection • Filtering • Joins • Aggregations • Sorting • Grouping • Pagination • Nesting • Windows

Slide 33

Slide 33 text

Groovy 4 Other Features: Type Annotations • Existing support • Now supported @Grab('net.jqwik:jqwik:1.6.5') import net.jqwik.api.* import net.jqwik.api.constraints.* class PropertyBasedTests { @Property def uniqueInList(@ForAll @Size(5) @UniqueElements List<@IntRange(min = 0, max = 10) Integer> aList) { assert aList.size() == aList.toSet().size() assert aList.every{ anInt -> anInt >= 0 && anInt <= 10 } } }

Slide 34

Slide 34 text

Type Annotations (Cont’d) @Grab('org.hibernate.validator:hibernate-validator:7.0.1.Final') @Grab('org.hibernate.validator:hibernate-validator-cdi:7.0.1.Final') @Grab('org.glassfish:jakarta.el:4.0.0') import jakarta.validation.constraints.* import jakarta.validation.* import groovy.transform.* @Canonical class Car { @NotNull @Size(min = 2, max = 14) String make @Min(1L) int seats List<@NotBlank String> owners } def validator = Validation.buildDefaultValidatorFactory().validator def violations = validator.validate(new Car(make: 'T', seats: 1)) assert violations*.message == ['size must be between 2 and 14'] violations = validator.validate(new Car(make: 'Tesla', owners: [''])) assert violations*.message.toSet() == ['must be greater than or equal to 1', 'must not be blank'] as Set violations = validator.validate(new Car(make: 'Tesla', owners: ['Elon'], seats: 2)) assert !violations

Slide 35

Slide 35 text

Built-in macro methods: SV, SVI, SVD, NV, NVL def num = 42 def list = [1 ,2, 3] def range = 0..5 def string = 'foo' println SV(num, list, range, string) println SVI(range) println SVD(range) num=42, list=[1, 2, 3], range=[0, 1, 2, 3, 4, 5], string=foo range=0..5 range=

Slide 36

Slide 36 text

TOML Builder/Slurper Incubating def ts = new TomlSlurper() def toml = ts.parseText(builder.toString()) assert 'HSV Maloo' == toml.records.car.name assert 'Holden' == toml.records.car.make assert 2006 == toml.records.car.year assert 'Australia' == toml.records.car.country assert 'http://example.org' == toml.records.car.homepage assert 'speed' == toml.records.car.record.type assert 'production pickup truck 271kph' == toml.records.car.record.description def builder = new TomlBuilder() builder.records { car { name 'HSV Maloo' make 'Holden' year 2006 country 'Australia' homepage new URL('http://example.org') record { type 'speed' description 'production pickup truck 271kph' } } }

Slide 37

Slide 37 text

JavaShell import org.apache.groovy.util.JavaShell def opts = ['--enable-preview', '--release', '14'] def src = 'record Coord(int x, int y) {}' Class coordClass = new JavaShell().compile('Coord', opts, src) assert coordClass.newInstance(5, 10).toString() == 'Coord[x=5, y=10]'

Slide 38

Slide 38 text

Improved Ranges def range = 1..5 assert range == [1, 2, 3, 4, 5] range = 1..<5 assert range == [1, 2, 3, 4] range = 1<..5 assert range == [2, 3, 4, 5] range = 1<..<5 assert range == [2, 3, 4] • Existing support • Now supported

Slide 39

Slide 39 text

AST Transformations: Groovy 2.4, Groovy 2.5, Groovy 3.0, Groovy 4.0* * Now also priorities for AST transforms in the same phase

Slide 40

Slide 40 text

@POJO 3 true Point(x:1, y:3) Point(x:2, y:2) Point(x:3, y:1) 2 Incubating

Slide 41

Slide 41 text

groovy-contracts module Design-by-contract import groovy.contracts.* @Invariant({ speed() >= 0 }) class Rocket { int speed = 0 boolean started = true @Requires({ isStarted() }) @Ensures({ old.speed < speed }) def accelerate(inc) { speed += inc } def isStarted() { started } def speed() { speed } } def r = new Rocket() r.accelerate(5)

Slide 42

Slide 42 text

Language Features • var for multi-assignment • JEP 445 Scripting enhancements • Improved type annotations • Underscore as placeholder AST transforms • @OperatorRename GDK enhancements • Array extension methods • File & path extensions • Collection extensions • Checked collections • Ascii barcharts Groovy 5 – Summary so far (In progress)

Slide 43

Slide 43 text

AST Transformations: Groovy 2.4, 2.5, 2.5 (improved), 3.0, 4.0, 5.0 • 80 AST transforms @NonSealed @RecordBase @Sealed @PlatformLog @GQ @Final @RecordType @POJO @Pure @Contracted @Ensures @Invariant @Requires @ClassInvariant @ContractElement @Postcondition @Precondition @OperatorRename

Slide 44

Slide 44 text

JEP 445 Scripting: Java • Standard class: • With JEP 445 Scripting (JDK 21 with preview enabled) public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } } void main() { System.out.println("Hello, World!"); }

Slide 45

Slide 45 text

JEP 445 Scripting: Groovy earlier versions • Standard class: • Static main method only: • Script: class HelloWorld { static main(args) { println 'Hello, World!' } } @CompileStatic static main(args) { println 'Hello, World!' } println 'Hello, World!' • Usually only used if you want to annotate the main method

Slide 46

Slide 46 text

JEP 445 Scripting: Groovy earlier versions • Standard class: • Static main method only: • Script: class HelloWorld { static main(args) { println 'Hello, World!' } } @CompileStatic static main(args) { println 'Hello, World!' } println 'Hello, World!' • Return type promoted to public void • Arguments promoted to String[] • Script class added • Has access to binding and context • Content added to run method • public static void main added creating instance and calling run • Variable declarations are local variables in run method or promoted to fields if annotated with @Field

Slide 47

Slide 47 text

JEP 445 Scripting: Groovy 5 • “Instance main” method: • “Instance run” method: @JsonIgnoreProperties(["binding"]) def run() { var mapper = new ObjectMapper() assert mapper.writeValueAsString(this) == '{"pets":["cat","dog"]}' } public pets = ['cat', 'dog'] def main() { assert upper(foo) + lower(bar) == 'FOObar' } def upper(s) { s.toUpperCase() } def lower = String::toLowerCase def (foo, bar) = ['Foo', 'Bar'] • Now also supported: o Instance main which are JEP 445 compatible o Instance run which remains a script • @Field now only needed for standard scripts

Slide 48

Slide 48 text

Scripting: compared to Java Earlier JDK versions JDK 21 with preview enabled Earlier Groovy versions Groovy 5 Traditional Java Class     Traditional Groovy Script     “instance run” script     “static main” script  JEP 445 Unnamed class & updated run protocol   “instance main” script    1 Promoted to standard “public static void main” 1 1 1 1 1 2 Access to script binding & context 2 2 2 2 3 Use new run protocol 4 Runnable on JDK11+ from Groovy 3 3 4 5 Uses @Field 5 5 5

Slide 49

Slide 49 text

Groovy 5: Other features • Underscore as placeholder and var for multi-assignment: // unused components in multi-assignment var (_, y, m, _, _, d) = Calendar.instance println "Today is $y-${m+1}-$d" // Today is 2023-8-23 // unused lambda parameters def c = (_, _, a, b) -> a + b def d = (_, a, _, b) -> a + b def e = (_, a, b, _) -> a + b assert c(1000, 100, 10, 1) == 11 assert d(1000, 100, 10, 1) == 101 assert e(1000, 100, 10, 1) == 110 // unused closure parameters def f = { a, _, _, b -> a + b } def g = { a, _, b, _ -> a + b } def h = { a, b, _, _ -> a + b } assert f(1000, 100, 10, 1) == 1001 assert g(1000, 100, 10, 1) == 1010 assert h(1000, 100, 10, 1) == 1100

Slide 50

Slide 50 text

Groovy 5: Other features • Ascii barchart: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'].each { println "\n${it.padRight(12)}${bar(it.size(), 0, 10, 10)}" }

Slide 51

Slide 51 text

Groovy 5: Other features • Checked collections: // assume type checking turned off List names = ['john', 'pete'] names << 'mary' // ok names << 35 // danger! but unnoticed at this point println names*.toUpperCase() // fails here // assume type checking turned off List names = ['john', 'pete'].asChecked(String) names << 'mary' // ok names << 35 // boom! fails early

Slide 52

Slide 52 text

Groovy 5: Other features • Set enhancements: var a = [1, 2, 3] as Set var b = [2, 3, 4] as Set assert a.union(b) == [1, 2, 3, 4] as Set assert a.intersect(b) == [2, 3] as Set assert (a | b) == [1, 2, 3, 4] as Set assert (a & b) == [2, 3] as Set assert (a ^ b) == [1, 4] as Set Set d = ['a', 'B', 'c'] Set e = ['A', 'b', 'D'] assert d.and(e, String.CASE_INSENSITIVE_ORDER) == ['a', 'B'] as Set assert e.union(d, String.CASE_INSENSITIVE_ORDER) == ['A', 'b', 'D', 'c'] as Set

Slide 53

Slide 53 text

Groovy 5: Other features • Additional “flatmap” functionality • flattenMany is a close cousin to collectMany var items = ["1", "2", "foo", "3", "bar"] var toInt = s -> s.number ? Optional.of(s.toInteger()) : Optional.empty() assert items.flattenMany(toInt) == [1, 2, 3] assert items.flattenMany(String::toList) == ['1', '2', 'f', 'o', 'o', '3', 'b', 'a', 'r'] assert items.flattenMany{ it.split(/[aeiou]/) } == ['1', '2', 'f', '3', 'b', 'r'] assert ['01/02/99', '12/12/23'].flattenMany{ it.split('/') } == ['01', '02', '99', '12', '12', '23']

Slide 54

Slide 54 text

Groovy 5: Other features • Additional variants of collectEntries for arrays, iterables and iterators with separate functions for transforming the keys and values def languages = ['Groovy', 'Java', 'Kotlin', 'Scala'] def collector = [clojure:7] assert languages.collectEntries(collector, String::toLowerCase, String::size) == [clojure:7, groovy:6, java:4, kotlin:6, scala:5] assert languages.withCollectedKeys(s -> s.take(1)) == [G:'Groovy', J:'Java', K:'Kotlin', S:'Scala'] assert languages.withCollectedValues(s -> s.size()) == [Groovy:6, Java:4, Kotlin:6, Scala:5]

Slide 55

Slide 55 text

Groovy 5: Other features • Similarly for maps… def lengths = [Groovy:6, Java:4, Kotlin:6, Scala:5] assert lengths.collectEntries(String::toLowerCase, { it ** 2 }) == [groovy:36, java:16, kotlin:36, scala:25] assert lengths.collectKeys{ it[0] } == [G:6, J:4, K:6, S:5] assert lengths.collectValues(Math.&pow.rcurry(2)) == [Groovy:36.0, Java:16.0, Kotlin:36.0, Scala:25.0] assert lengths.collectValues(Math.&pow.curry(2).memoize()) == [Groovy:64.0, Java:16.0, Kotlin:64.0, Scala:32.0]

Slide 56

Slide 56 text

Groovy 5: ArrayGroovyMethods enhancements int[] nums = -3..2 assert nums.any{ it > 1 } && nums.every(n -> n < 4) && nums.join(' ') == '-3 -2 -1 0 1 2' && nums.head() == -3 && nums.tail() == -2..2 && nums.max() == 2 && nums.min{ it.abs() } == 0 && nums.maxComparing(Comparator.reverseOrder()) == -3 && nums.reverse() == 2..-3 int[][] matrix = [[1, 2], [10, 20], [100, 200]] assert matrix.transpose() == [[1, 10, 100], [2, 20, 200]]

Slide 57

Slide 57 text

Ecosystem update • Grails and Gradle have moved to recent Groovy 3 versions • Micronaut, Spock, Geb all have Groovy 4 support

Slide 58

Slide 58 text

Ecosystem update: Groovy 4 Micronautfw @MappedEntity @EqualsAndHashCode(excludes = 'id') @Serdeable @CompileStatic @ToString(excludes = 'id', includeNames = true, ignoreNulls = true) @JsonIgnoreProperties(['id', 'done', 'scheduled']) class Todo { @GeneratedValue @Id Long id @NotBlank final String title @Nullable final String description @Nullable LocalDate due = null @Nullable LocalDate completed Todo(@NotBlank String title, @Nullable String description, @Nullable LocalDate due, @Nullable LocalDate completed) { this.title = title this.description = description this.due = due this.completed = completed } boolean isDone() { completed != null } boolean isScheduled() { due != null } }

Slide 59

Slide 59 text

Ecosystem update: Groovy 4 Micronautfw @ExecuteOn(BLOCKING) @Controller('todo') @CompileStatic class TodoController { @Inject TodoService service @Inject ObjectMapper mapper @Get('/') Collection list() { service.findAll() } @Post('/') @Status(HttpStatus.CREATED) Todo create(@NotNull @Valid @Body Todo todo) { service.create(todo) } @Post('/find') Todo find(@NotNull @Valid @Body TodoKey key) { service.find(key) } @Get('/stats') String statsAsString() { mapper.writeValueAsString(service.stats()) } …

Slide 60

Slide 60 text

Ecosystem update: Groovy 4 Micronautfw … @Delete('/delete/{title}') Todo delete(@PathVariable String title) { delete(title, null) } @Delete('/delete/{title}/{due}') Todo delete(@PathVariable String title, @PathVariable LocalDate due) { service.delete(new TodoKey(title, due)) } @Put('/reschedule/{newDate}') Todo reschedule(@NotNull @Valid @Body TodoKey key, @PathVariable LocalDate newDate) { service.reschedule(key, newDate) } @Put('/unschedule') Todo unschedule(@NotNull @Valid @Body TodoKey key) { service.unschedule(key) } @Put('/complete') Todo complete(@NotNull @Valid @Body TodoKey key) { service.complete(key) } }

Slide 61

Slide 61 text

Ecosystem update: Groovy 4 Micronautfw @MicronautTest class TodoClientSpec extends Specification { @Inject @Client TodoClient client void 'test interaction using declarative client'() { when: var day1 = LocalDate.of(2023, 9, 1) var day2 = LocalDate.of(2023, 9, 2) client.create(new Todo('Create Todo class', null, day1, null)) client.create(new Todo('Create TodoKey class', null, day1, null)) client.create(new Todo('Create TodoStats class', null, day1, null)) client.create(new Todo('Create repo classes', null, day2, null)) client.create(new Todo('Declarative client example', null, day2, null)) client.create(new Todo('Create test classes', null, day2, null)) …

Slide 62

Slide 62 text

Ecosystem update: Groovy 4 Micronautfw … then: GQ { from todo in client.list() groupby todo.due orderby todo.due select todo.due, list(todo.title) as todos_due }.toString() == ''' +------------+------------------------------------------------------------------------+ | due | todos_due | +------------+------------------------------------------------------------------------+ | 2023-09-01 | [Create Todo class, Create TodoKey class, Create TodoStats class] | | 2023-09-02 | [Create repo classes, Declarative client example, Create test classes] | +------------+------------------------------------------------------------------------+ '''.stripIndent() } }

Slide 63

Slide 63 text

Ecosystem update: Iris Classification with GraalVM & Deep Netts

Slide 64

Slide 64 text

Ecosystem update: Iris Classification

Slide 65

Slide 65 text

Ecosystem update: Iris Classification

Slide 66

Slide 66 text

On-going research for future Groovy versions • Additional switch destructuring/pattern matching • instanceof “pattern matching” • Smarter type checking: non-null, pure • Module definitions in Groovy • Syntactic sugar wrapper for JDK11 HttpClient • Type safe access to XML, JSON, YAML, CSV ala Manifold

Slide 67

Slide 67 text

The Groovy future is looking good • Healthy community • Downloads still increasing • Strong usage of Groovy by many frameworks • Increasing usage of Groovy in data science as the “Python” for the JVM

Slide 68

Slide 68 text

Join us: groovy.apache.org