You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@groovy.apache.org by Paul King <pa...@asert.com.au> on 2021/11/01 15:14:28 UTC

Record enhancements

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

Re: Re: [EXT] Re: Record enhancements

Posted by Daniel Sun <su...@apache.org>.
> That was not easy to do with the current grammar hence the limitation. I
was hoping we would find a way to remove the limitation before 4GA hence it
possibly isn't mentioned yet in the documentation/release notes. The
working of how the compact constructor was "plumbed in" has changed
slightly - perhaps we can re-look whether removing that limitation is
easier now.

Too busy recently... I will try to find some spare time to look into the issue.

Cheers,
Daniel Sun
On 2021/11/10 08:22:35 Paul King wrote:
> Comments inline.
> 
> On Wed, Nov 10, 2021 at 1:17 AM Milles, Eric (TR Technology) <
> eric.milles@thomsonreuters.com> wrote:
> 
> > A couple questions regarding record types:
> >
> >
> >
> > record Person(String name, Date dob) {
> >
> >   public Person {
> >
> >     // ...
> >
> >   }
> >
> > }
> >
> >
> >
> > 1) Was it by design to have Person(String) and Person() constructors
> > created for the example above?  I would prefer to see the canonical
> > constructor and the map constructor only.  That is,
> > TupleConstructor(defaults=false).  Then if I wanted the others, I can add
> > default arguments to any of the components or set defaults to true
> > explicitly.
> >
> 
> The current implementation is by design. "@RecordType" expands to (among
> other things) "@TupleConstructor(namedVariant = true, force = true)" and
> doesn't include "defaults = false". The way we do our merging given we
> currently have PREFER_EXPLICIT_MERGED as the collector mode meant that if I
> had "defaults = false" explicitly it wasn't allowing "defaults = true" to
> be added. Merging was overriding it. We possibly need to re-look at that
> merging behavior and then flipping the defaults boolean is a workable way
> forward.
> 
> 
> > 2) Is the modifier required for the compact constructor by design?  If I
> > remove "public" above it fails to parse.  Java allows the compact
> > constructor without visibility modifier.
> >
> 
> That was not easy to do with the current grammar hence the limitation. I
> was hoping we would find a way to remove the limitation before 4GA hence it
> possibly isn't mentioned yet in the documentation/release notes. The
> working of how the compact constructor was "plumbed in" has changed
> slightly - perhaps we can re-look whether removing that limitation is
> easier now.
> 
> 
> > 3) Did the copyWith() or components() ideas get dropped?  I am not seeing
> > them in 4.0b2.
> >
> 
> They currently default to false but you can turn them on with the
> respective boolean annotation attributes in RecordOptions. I set them to
> false because:
> * copyWith() is also false in ImmutableBase, so I left it at the same
> default value as that for consistency and you can get a long way with
> toList/toMap and the existing constructors.
> * components() only works for records having a limited number of components
> up to our largest TupleN class (currently Tuple16), so I figured users who
> turn that on will know what they are doing and won't be surprised if they
> get a compiler error for 17 components (say)
> 
> 4) Since the compact constructor is implemented via
> > TupleConstructor(pre=...), there is no error or warning if I provide a pre
> > closure and a compact constructor.  Should there be an error or warning?
> >
> 
> I am not sure it would always be an error/warning or if some natural
> merging could be feasible but I suspect it isn't covered well by existing
> tests. Any help in that area would be greatly appreciated.
> 
> 
> 
> >
> >
> >
> >
> > *From:* OCsite <oc...@ocs.cz>
> > *Sent:* Tuesday, November 2, 2021 2:07 PM
> > *To:* MG <mg...@arscreat.com>
> > *Cc:* Groovy_Developers <de...@groovy.apache.org>; paulk@asert.com.au
> > *Subject:* [EXT] Re: Record enhancements
> >
> >
> >
> > *External Email:* Use caution with links and attachments.
> >
> >
> >
> > As for tersity, I presume the actual usage would look like „foo as Map“ or
> > „foo as List“ anyway, which is actually one less keypress :), and — which
> > in my personal opinion is considerably more important — it offers better
> > consistency and polymorphism.
> >
> >
> >
> > (I know next to nothing of Intellisense, but I guess it should offer the
> > *as* operator with a selection of known types, should it not?)
> >
> >
> >
> > Still I might be missing something of importance, of course.
> >
> >
> >
> > All the best,
> >
> > OC
> >
> >
> >
> > On 2 Nov 2021, at 19:17, MG <mg...@arscreat.com> wrote:
> >
> >
> >
> > Hmmm, yes, that would be an option.
> > More terse & can be discovered via Intellisense are two reasons I could
> > think of that speak for the toList()/toMap() approach...
> >
> > Cheers,
> > mg
> >
> > On 02/11/2021 12:48, OCsite wrote:
> >
> > Hi there,
> >
> > I am probably missing something obvious here, but why adding separate
> > methods for this instead of simply reusing asType?
> >
> > Thanks and all the best,
> > OC
> >
> >
> > On 2. 11. 2021, at 8:35, Paul King <pa...@asert.com.au> wrote:
> >
> > Thanks for the feedback! I added "toMap()" to the PR.
> >
> > On Tue, Nov 2, 2021 at 7:02 AM MG <mg...@arscreat.com> wrote:
> >
> > Hi Paul,
> >
> > quick "from the top of my head" reply:
> >
> > copyWith(...): Sounds like a  great idea, I have record-like classes in
> > use, and the need for something like this arises immediately in practice
> > getAt(int): Don't see why not, might be useful
> > 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.
> > components(): Same as getAt; the name seems quite long, maybe we can come
> > up with something more terse ?
> > 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
> > <https://urldefense.com/v3/__https:/github.com/apache/groovy/blob/master/src/spec/doc/_records.adoc__;!!GFN0sa3rsbfR8OLyAw!NyOeTO41BsAx72RYKaFkHpjzc7rfbQO2ajvOqriwA1vxqYxsGsMsTEVmIQKC-qtDO--XcfI$>
> > [2]
> > https://github.com/apache/groovy/blob/master/src/spec/test/RecordSpecificationTest.groovy
> > <https://urldefense.com/v3/__https:/github.com/apache/groovy/blob/master/src/spec/test/RecordSpecificationTest.groovy__;!!GFN0sa3rsbfR8OLyAw!NyOeTO41BsAx72RYKaFkHpjzc7rfbQO2ajvOqriwA1vxqYxsGsMsTEVmIQKC-qtDCIm0GQs$>
> > [3]
> > https://github.com/apache/groovy-website/blob/asf-site/site/src/site/wiki/GEP-14.adoc
> > <https://urldefense.com/v3/__https:/github.com/apache/groovy-website/blob/asf-site/site/src/site/wiki/GEP-14.adoc__;!!GFN0sa3rsbfR8OLyAw!NyOeTO41BsAx72RYKaFkHpjzc7rfbQO2ajvOqriwA1vxqYxsGsMsTEVmIQKC-qtDPjyENMk$>
> > [4] https://issues.apache.org/jira/browse/GROOVY-10338
> > <https://urldefense.com/v3/__https:/issues.apache.org/jira/browse/GROOVY-10338__;!!GFN0sa3rsbfR8OLyAw!NyOeTO41BsAx72RYKaFkHpjzc7rfbQO2ajvOqriwA1vxqYxsGsMsTEVmIQKC-qtDt01uPzI$>
> >
> >
> >
> >
> >
> 

Re: [EXT] Re: Record enhancements

Posted by Paul King <pa...@asert.com.au>.
Comments inline.

On Wed, Nov 10, 2021 at 1:17 AM Milles, Eric (TR Technology) <
eric.milles@thomsonreuters.com> wrote:

> A couple questions regarding record types:
>
>
>
> record Person(String name, Date dob) {
>
>   public Person {
>
>     // ...
>
>   }
>
> }
>
>
>
> 1) Was it by design to have Person(String) and Person() constructors
> created for the example above?  I would prefer to see the canonical
> constructor and the map constructor only.  That is,
> TupleConstructor(defaults=false).  Then if I wanted the others, I can add
> default arguments to any of the components or set defaults to true
> explicitly.
>

The current implementation is by design. "@RecordType" expands to (among
other things) "@TupleConstructor(namedVariant = true, force = true)" and
doesn't include "defaults = false". The way we do our merging given we
currently have PREFER_EXPLICIT_MERGED as the collector mode meant that if I
had "defaults = false" explicitly it wasn't allowing "defaults = true" to
be added. Merging was overriding it. We possibly need to re-look at that
merging behavior and then flipping the defaults boolean is a workable way
forward.


> 2) Is the modifier required for the compact constructor by design?  If I
> remove "public" above it fails to parse.  Java allows the compact
> constructor without visibility modifier.
>

That was not easy to do with the current grammar hence the limitation. I
was hoping we would find a way to remove the limitation before 4GA hence it
possibly isn't mentioned yet in the documentation/release notes. The
working of how the compact constructor was "plumbed in" has changed
slightly - perhaps we can re-look whether removing that limitation is
easier now.


> 3) Did the copyWith() or components() ideas get dropped?  I am not seeing
> them in 4.0b2.
>

They currently default to false but you can turn them on with the
respective boolean annotation attributes in RecordOptions. I set them to
false because:
* copyWith() is also false in ImmutableBase, so I left it at the same
default value as that for consistency and you can get a long way with
toList/toMap and the existing constructors.
* components() only works for records having a limited number of components
up to our largest TupleN class (currently Tuple16), so I figured users who
turn that on will know what they are doing and won't be surprised if they
get a compiler error for 17 components (say)

4) Since the compact constructor is implemented via
> TupleConstructor(pre=...), there is no error or warning if I provide a pre
> closure and a compact constructor.  Should there be an error or warning?
>

I am not sure it would always be an error/warning or if some natural
merging could be feasible but I suspect it isn't covered well by existing
tests. Any help in that area would be greatly appreciated.



>
>
>
>
> *From:* OCsite <oc...@ocs.cz>
> *Sent:* Tuesday, November 2, 2021 2:07 PM
> *To:* MG <mg...@arscreat.com>
> *Cc:* Groovy_Developers <de...@groovy.apache.org>; paulk@asert.com.au
> *Subject:* [EXT] Re: Record enhancements
>
>
>
> *External Email:* Use caution with links and attachments.
>
>
>
> As for tersity, I presume the actual usage would look like „foo as Map“ or
> „foo as List“ anyway, which is actually one less keypress :), and — which
> in my personal opinion is considerably more important — it offers better
> consistency and polymorphism.
>
>
>
> (I know next to nothing of Intellisense, but I guess it should offer the
> *as* operator with a selection of known types, should it not?)
>
>
>
> Still I might be missing something of importance, of course.
>
>
>
> All the best,
>
> OC
>
>
>
> On 2 Nov 2021, at 19:17, MG <mg...@arscreat.com> wrote:
>
>
>
> Hmmm, yes, that would be an option.
> More terse & can be discovered via Intellisense are two reasons I could
> think of that speak for the toList()/toMap() approach...
>
> Cheers,
> mg
>
> On 02/11/2021 12:48, OCsite wrote:
>
> Hi there,
>
> I am probably missing something obvious here, but why adding separate
> methods for this instead of simply reusing asType?
>
> Thanks and all the best,
> OC
>
>
> On 2. 11. 2021, at 8:35, Paul King <pa...@asert.com.au> wrote:
>
> Thanks for the feedback! I added "toMap()" to the PR.
>
> On Tue, Nov 2, 2021 at 7:02 AM MG <mg...@arscreat.com> wrote:
>
> Hi Paul,
>
> quick "from the top of my head" reply:
>
> copyWith(...): Sounds like a  great idea, I have record-like classes in
> use, and the need for something like this arises immediately in practice
> getAt(int): Don't see why not, might be useful
> 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.
> components(): Same as getAt; the name seems quite long, maybe we can come
> up with something more terse ?
> 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
> <https://urldefense.com/v3/__https:/github.com/apache/groovy/blob/master/src/spec/doc/_records.adoc__;!!GFN0sa3rsbfR8OLyAw!NyOeTO41BsAx72RYKaFkHpjzc7rfbQO2ajvOqriwA1vxqYxsGsMsTEVmIQKC-qtDO--XcfI$>
> [2]
> https://github.com/apache/groovy/blob/master/src/spec/test/RecordSpecificationTest.groovy
> <https://urldefense.com/v3/__https:/github.com/apache/groovy/blob/master/src/spec/test/RecordSpecificationTest.groovy__;!!GFN0sa3rsbfR8OLyAw!NyOeTO41BsAx72RYKaFkHpjzc7rfbQO2ajvOqriwA1vxqYxsGsMsTEVmIQKC-qtDCIm0GQs$>
> [3]
> https://github.com/apache/groovy-website/blob/asf-site/site/src/site/wiki/GEP-14.adoc
> <https://urldefense.com/v3/__https:/github.com/apache/groovy-website/blob/asf-site/site/src/site/wiki/GEP-14.adoc__;!!GFN0sa3rsbfR8OLyAw!NyOeTO41BsAx72RYKaFkHpjzc7rfbQO2ajvOqriwA1vxqYxsGsMsTEVmIQKC-qtDPjyENMk$>
> [4] https://issues.apache.org/jira/browse/GROOVY-10338
> <https://urldefense.com/v3/__https:/issues.apache.org/jira/browse/GROOVY-10338__;!!GFN0sa3rsbfR8OLyAw!NyOeTO41BsAx72RYKaFkHpjzc7rfbQO2ajvOqriwA1vxqYxsGsMsTEVmIQKC-qtDt01uPzI$>
>
>
>
>
>

RE: [EXT] Re: Record enhancements

Posted by "Milles, Eric (TR Technology)" <er...@thomsonreuters.com>.
A couple questions regarding record types:

record Person(String name, Date dob) {
  public Person {
    // ...
  }
}

1) Was it by design to have Person(String) and Person() constructors created for the example above?  I would prefer to see the canonical constructor and the map constructor only.  That is, TupleConstructor(defaults=false).  Then if I wanted the others, I can add default arguments to any of the components or set defaults to true explicitly.

2) Is the modifier required for the compact constructor by design?  If I remove "public" above it fails to parse.  Java allows the compact constructor without visibility modifier.

3) Did the copyWith() or components() ideas get dropped?  I am not seeing them in 4.0b2.

4) Since the compact constructor is implemented via TupleConstructor(pre=...), there is no error or warning if I provide a pre closure and a compact constructor.  Should there be an error or warning?


From: OCsite <oc...@ocs.cz>
Sent: Tuesday, November 2, 2021 2:07 PM
To: MG <mg...@arscreat.com>
Cc: Groovy_Developers <de...@groovy.apache.org>; paulk@asert.com.au
Subject: [EXT] Re: Record enhancements

External Email: Use caution with links and attachments.

As for tersity, I presume the actual usage would look like „foo as Map“ or „foo as List“ anyway, which is actually one less keypress :), and — which in my personal opinion is considerably more important — it offers better consistency and polymorphism.

(I know next to nothing of Intellisense, but I guess it should offer the as operator with a selection of known types, should it not?)

Still I might be missing something of importance, of course.

All the best,
OC


On 2 Nov 2021, at 19:17, MG <mg...@arscreat.com>> wrote:

Hmmm, yes, that would be an option.
More terse & can be discovered via Intellisense are two reasons I could think of that speak for the toList()/toMap() approach...

Cheers,
mg

On 02/11/2021 12:48, OCsite wrote:

Hi there,

I am probably missing something obvious here, but why adding separate methods for this instead of simply reusing asType?

Thanks and all the best,
OC


On 2. 11. 2021, at 8:35, Paul King <pa...@asert.com.au>> wrote:

Thanks for the feedback! I added "toMap()" to the PR.

On Tue, Nov 2, 2021 at 7:02 AM MG <mg...@arscreat.com>> wrote:

Hi Paul,

quick "from the top of my head" reply:

copyWith(...): Sounds like a  great idea, I have record-like classes in use, and the need for something like this arises immediately in practice
getAt(int): Don't see why not, might be useful
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.
components(): Same as getAt; the name seems quite long, maybe we can come up with something more terse ?
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<https://urldefense.com/v3/__https:/github.com/apache/groovy/blob/master/src/spec/doc/_records.adoc__;!!GFN0sa3rsbfR8OLyAw!NyOeTO41BsAx72RYKaFkHpjzc7rfbQO2ajvOqriwA1vxqYxsGsMsTEVmIQKC-qtDO--XcfI$>
[2] https://github.com/apache/groovy/blob/master/src/spec/test/RecordSpecificationTest.groovy<https://urldefense.com/v3/__https:/github.com/apache/groovy/blob/master/src/spec/test/RecordSpecificationTest.groovy__;!!GFN0sa3rsbfR8OLyAw!NyOeTO41BsAx72RYKaFkHpjzc7rfbQO2ajvOqriwA1vxqYxsGsMsTEVmIQKC-qtDCIm0GQs$>
[3] https://github.com/apache/groovy-website/blob/asf-site/site/src/site/wiki/GEP-14.adoc<https://urldefense.com/v3/__https:/github.com/apache/groovy-website/blob/asf-site/site/src/site/wiki/GEP-14.adoc__;!!GFN0sa3rsbfR8OLyAw!NyOeTO41BsAx72RYKaFkHpjzc7rfbQO2ajvOqriwA1vxqYxsGsMsTEVmIQKC-qtDPjyENMk$>
[4] https://issues.apache.org/jira/browse/GROOVY-10338<https://urldefense.com/v3/__https:/issues.apache.org/jira/browse/GROOVY-10338__;!!GFN0sa3rsbfR8OLyAw!NyOeTO41BsAx72RYKaFkHpjzc7rfbQO2ajvOqriwA1vxqYxsGsMsTEVmIQKC-qtDt01uPzI$>




Re: Record enhancements

Posted by OCsite <oc...@ocs.cz>.
As for tersity, I presume the actual usage would look like „foo as Map“ or „foo as List“ anyway, which is actually one less keypress :), and — which in my personal opinion is considerably more important — it offers better consistency and polymorphism.

(I know next to nothing of Intellisense, but I guess it should offer the as operator with a selection of known types, should it not?)

Still I might be missing something of importance, of course.

All the best,
OC

> On 2 Nov 2021, at 19:17, MG <mg...@arscreat.com> wrote:
> 
> Hmmm, yes, that would be an option.
> More terse & can be discovered via Intellisense are two reasons I could think of that speak for the toList()/toMap() approach...
> 
> Cheers,
> mg
> 
> On 02/11/2021 12:48, OCsite wrote:
>> Hi there,
>> 
>> I am probably missing something obvious here, but why adding separate methods for this instead of simply reusing asType?
>> 
>> Thanks and all the best,
>> OC
>> 
>>> On 2. 11. 2021, at 8:35, Paul King <pa...@asert.com.au> wrote:
>>> 
>>> Thanks for the feedback! I added "toMap()" to the PR.
>>> 
>>> On Tue, Nov 2, 2021 at 7:02 AM MG <mg...@arscreat.com> wrote:
>>>> Hi Paul,
>>>> 
>>>> quick "from the top of my head" reply:
>>>> 
>>>> copyWith(...): Sounds like a  great idea, I have record-like classes in use, and the need for something like this arises immediately in practice
>>>> getAt(int): Don't see why not, might be useful
>>>> 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.
>>>> components(): Same as getAt; the name seems quite long, maybe we can come up with something more terse ?
>>>> 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
>>>> 
>>>> 
> 


Re: Record enhancements

Posted by MG <mg...@arscreat.com>.
Hmmm, yes, that would be an option.
More terse & can be discovered via Intellisense are two reasons I could 
think of that speak for the toList()/toMap() approach...

Cheers,
mg

On 02/11/2021 12:48, OCsite wrote:
> Hi there,
>
> I am probably missing something obvious here, but why adding separate methods for this instead of simply reusing asType?
>
> Thanks and all the best,
> OC
>
>> On 2. 11. 2021, at 8:35, Paul King <pa...@asert.com.au> wrote:
>>
>> Thanks for the feedback! I added "toMap()" to the PR.
>>
>> On Tue, Nov 2, 2021 at 7:02 AM MG <mg...@arscreat.com> wrote:
>>> Hi Paul,
>>>
>>> quick "from the top of my head" reply:
>>>
>>> copyWith(...): Sounds like a  great idea, I have record-like classes in use, and the need for something like this arises immediately in practice
>>> getAt(int): Don't see why not, might be useful
>>> 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.
>>> components(): Same as getAt; the name seems quite long, maybe we can come up with something more terse ?
>>> 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
>>>
>>>


Re: Record enhancements

Posted by OCsite <oc...@ocs.cz>.
Hi there,

I am probably missing something obvious here, but why adding separate methods for this instead of simply reusing asType?

Thanks and all the best,
OC

> On 2. 11. 2021, at 8:35, Paul King <pa...@asert.com.au> wrote:
> 
> Thanks for the feedback! I added "toMap()" to the PR.
> 
> On Tue, Nov 2, 2021 at 7:02 AM MG <mg...@arscreat.com> wrote:
>> 
>> Hi Paul,
>> 
>> quick "from the top of my head" reply:
>> 
>> copyWith(...): Sounds like a  great idea, I have record-like classes in use, and the need for something like this arises immediately in practice
>> getAt(int): Don't see why not, might be useful
>> 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.
>> components(): Same as getAt; the name seems quite long, maybe we can come up with something more terse ?
>> 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
>> 
>> 


Re: Record enhancements

Posted by Paul King <pa...@asert.com.au>.
Thanks for the feedback! I added "toMap()" to the PR.

On Tue, Nov 2, 2021 at 7:02 AM MG <mg...@arscreat.com> wrote:
>
> Hi Paul,
>
> quick "from the top of my head" reply:
>
> copyWith(...): Sounds like a  great idea, I have record-like classes in use, and the need for something like this arises immediately in practice
> getAt(int): Don't see why not, might be useful
> 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.
> components(): Same as getAt; the name seems quite long, maybe we can come up with something more terse ?
> 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
>
>

Re: Record enhancements

Posted by MG <mg...@arscreat.com>.
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