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

Groovy Roadmap

Groovy Roadmap

Groovy 4 was released in early 2022, alpha versions of Groovy 5 have been released in recent months. Groovy 4 includes switch expressions, sealed types, records, additional module re-working, bundled type checker extensions, bundled macro methods, a JavaShell, some additional AST transformations, and a myriad of other new miscellaneous features. Groovy 5 includes improved scripting support, many GDK enhancements, var for multi-assignment and underscore as a placeholder.

paulking

May 03, 2022
Tweet

More Decks by paulking

Other Decks in Programming

Transcript

  1. 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
  2. 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]
  3. 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
  4. Groovy: How it started If Java/the JDK is your chocolate

    cake… Groovy is the Icing! Quote circa 2007 Java Groovy
  5. 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
  6. 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)
  7. 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
  8. 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
  9. 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 }
  10. 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  
  11. 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
  12. @RecordType Alternative style for: @RecordType class Cyclist { String firstName

    String lastName } def richie = new Cyclist('Richie', 'Porte') Incubating record Cyclist(String firstName, String lastName) { }
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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)
  18. 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) { }
  19. 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) { }
  20. 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<String> skills) { @Builder Developer(Integer id, String full, String email, List<String> skills) { this(id, full.split(' ')[0], full.split(' ')[1], email, skills) } }
  21. 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())
  22. record Quadratic(double a, double b = 0, double c =

    0) { @Newify(Complex) List<Complex> 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<Complex> 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
  23. 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
  24. 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)   
  25. 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+
  26. Sealed Types – Good for ADTs @Sealed interface Tree<T> {}

    @Singleton final class Empty implements Tree { String toString() { 'Empty' } } @Canonical final class Node<T> implements Tree<T> { T value Tree<T> left, right } Tree<Integer> tree = new Node<>(42, new Node<>(0, Empty.instance, Empty.instance), Empty.instance) assert tree.toString() == 'Node(42, Node(0, Empty, Empty), Empty)'
  27. 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 { }
  28. 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
  29. 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 { }
  30. 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
  31. 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
  32. 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
  33. 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 } } }
  34. 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
  35. 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=<groovy.lang.IntRange@14 from=0 to=5 reverse=false inclusiveRight=true inclusiveLeft=true def r = NV(range) assert r instanceof NamedValue assert r.name == 'range' && r.val == 0..5
  36. 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' } } }
  37. 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]'
  38. 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
  39. AST Transformations: Groovy 2.4, Groovy 2.5, Groovy 3.0, Groovy 4.0*

    * Now also priorities for AST transforms in the same phase
  40. 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)
  41. 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)
  42. 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
  43. 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!"); }
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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)}" }
  50. Groovy 5: Other features • Checked collections: // assume type

    checking turned off List<String> 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<String> names = ['john', 'pete'].asChecked(String) names << 'mary' // ok names << 35 // boom! fails early
  51. 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
  52. 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']
  53. 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]
  54. 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]
  55. 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]]
  56. Ecosystem update • Grails and Gradle have moved to recent

    Groovy 3 versions • Micronaut, Spock, Geb all have Groovy 4 support
  57. 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 } }
  58. Ecosystem update: Groovy 4 Micronautfw @ExecuteOn(BLOCKING) @Controller('todo') @CompileStatic class TodoController

    { @Inject TodoService service @Inject ObjectMapper mapper @Get('/') Collection<Todo> 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()) } …
  59. 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) } }
  60. 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)) …
  61. 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() } }
  62. 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
  63. 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