Hi Paul, quick "from the top of my head" reply:
1. copyWith(...): Sounds like a great idea, I have record-like classes in use, and the need for something like this arises immediately in practice 2. getAt(int): Don't see why not, might be useful 3. toList(): Destructuring should show its strength when pattern matching is introduced - outside of pattern matching right now I don't see much application/need for such functionality (but would be interested to see some practical examples :-) ), but having it does not seem to hurt. 4. components(): Same as getAt; the name seems quite long, maybe we can come up with something more terse ? 5. toMap(): If we have toList(), would a toMap() make sense, so that the map could be modified and passed as a record ctor argument to create a new record ? Cheers, mg On 01/11/2021 16:14, Paul King wrote:
Hi folks, I will be ready for a new Groovy 4 release shortly. I am interested in folks' thoughts on records as they have gone through a few changes recently (documented in [1], [2] and [3]) and there is a proposal[4] for a few more enhancements. There is a "copyWith" method (still undergoing some refactoring) similar to the copy method in Scala and Kotlin which allows one record to be defined in terms of another. It can be disabled if you really must have Java-like records. The refactoring of that method hit a slight glitch, so might not work if you grab the latest source but should be fixed shortly. record Fruit(String name, double price) {} def apple = new Fruit('Apple', 11.6) assert apply.toString() == 'Fruit[name=Apple, price=11.6]' def orange = apple.copyWith(name: 'Orange') assert orange.toString() == 'Fruit[name=Orange, price=11.6]' There is a "getAt(int)" method to return e.g. the first component with myRecord[0] following similar Groovy conventions for other aggregates. This is mostly targeted at dynamic Groovy as it conveys no typing information. Similarly, there is a "toList" (current name but suggestions welcome) method which returns a Tuple (which is also a list) to return all of the components (again with typing information). record Point(int x, int y, String color) {} def p = new Point(100, 200, 'green') assert p[0] == 100 assert p[1] == 200 assert p[2] == 'green' def (x, y, c) = p.toList() assert x == 100 assert y == 200 assert c == 'green' There is also an optional (turned on by an annotation attribute) "components" method which returns all components as a typed tuple, e.g. Tuple1, Tuple2, etc. This is useful for Groovy's static nature and is automatically handled by current destructuring (see the tests in the PR). The limitation is that we currently only go to Tuple16 with our tuple types - which is why I made it disabled by default. @RecordBase(componentTuple=true) record Point(int x, int y, String color) { } @TypeChecked def method() { def p1 = new Point(100, 200, 'green') def (int x1, int y1, String c1) = p1.components() assert x1 == 100 assert y1 == 200 assert c1 == 'green' def p2 = new Point(10, 20, 'blue') def (x2, y2, c2) = p2.components() assert x2 * 10 == 100 assert y2 ** 2 == 400 assert c2.toUpperCase() == 'BLUE' } An alternative would be to follow Kotlin's approach and just have typed methods like "component1", "component2", etc. We might want to follow that convention or we might want to follow our TupleN naming, e.g. "getV1", "getV2", etc. We would need to augment the Groovy runtime and type checker to know about records if we wanted to support destructuring but we could avoid the "toList" method and "components" method with its size limitation if we did add such support. Any feedback welcome, Cheers, Paul. P.S. Records are an incubating feature - hence may change in backwards incompatible ways, particularly until we hit Groovy 4 final. [1]https://github.com/apache/groovy/blob/master/src/spec/doc/_records.adoc [2]https://github.com/apache/groovy/blob/master/src/spec/test/RecordSpecificationTest.groovy [3]https://github.com/apache/groovy-website/blob/asf-site/site/src/site/wiki/GEP-14.adoc [4]https://issues.apache.org/jira/browse/GROOVY-10338