Proposal: Change map access semantics in 4.0

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Proposal: Change map access semantics in 4.0

Christopher Smith
This is a proposal for a breaking change for how Groovy interacts with
objects implementing the Map interface.

== Background ==

Groovy supports looking up the values of a map by converting
property-style access `map.foo` to `map.get('foo')`. My understanding
is that the purpose of this feature is to allow drop-in substitution
of a map for a POJO.

Currently, property-style access is always(?) routed to the Map's
`get` method, regardless of any actual properties available on the
object. This is the case even in static compilation where the
variable's declared type implements a property matching the attempted
access. This behavior leads to unexpected results and even heap
pollution.

As two examples, the `isEmpty()` method is defined for all collections
and returns a primitive boolean. `list.empty` and `set.empty`
correctly return true/false depending on the contents of the
collection, but `map.empty` is interpreted as a request to
`map.get('empty')`. This is entirely surprising, especially when it
results in an NPE (when for some reason the null isn't as-booleaned).

Additionally, some classes implementing Map also provide other
ergonomics, such as Spring's HttpHeaders, which eventually implements
`Map<String, List<String>>`. It has explicit computed properties for
most of the common headers, with such property types as URI (Location)
and long (Last-Modified). Groovy, however, interprets
`headers.location` as a call to `headers.get('location')` and returns
a `List<String>` instead of `headers.getLocation()`.

This feature provides novel and evening-occupying results
(particularly as Eclipse's hover and autocomplete suggest the expected
behavior!).

== Proposal ==

Starting in Groovy 4, prioritize explicitly defined properties for
resolution ahead of `Map#get`.

If some object implementing Map actually defines properties, then it
seems to me a reasonable guess that that support is based on the
intended/expected usage patterns, and that users employing that object
would expect to have it available. In the case where clients
specifically want to use a lookup, get and `[]` are easily available.

--
Christopher Smith
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Change map access semantics in 4.0

paulk_asert
Administrator

Thanks for the email. It has been discussed numerous times before but that
doesn't mean we can't re-look at certain aspects. Just to clarify, there is no issue
when using the full getter name for property getter, correct? E.g. map.isEmpty()

Cheers, Paul.

On Thu, Nov 12, 2020 at 9:46 AM Christopher Smith <[hidden email]> wrote:
This is a proposal for a breaking change for how Groovy interacts with
objects implementing the Map interface.

== Background ==

Groovy supports looking up the values of a map by converting
property-style access `map.foo` to `map.get('foo')`. My understanding
is that the purpose of this feature is to allow drop-in substitution
of a map for a POJO.

Currently, property-style access is always(?) routed to the Map's
`get` method, regardless of any actual properties available on the
object. This is the case even in static compilation where the
variable's declared type implements a property matching the attempted
access. This behavior leads to unexpected results and even heap
pollution.

As two examples, the `isEmpty()` method is defined for all collections
and returns a primitive boolean. `list.empty` and `set.empty`
correctly return true/false depending on the contents of the
collection, but `map.empty` is interpreted as a request to
`map.get('empty')`. This is entirely surprising, especially when it
results in an NPE (when for some reason the null isn't as-booleaned).

Additionally, some classes implementing Map also provide other
ergonomics, such as Spring's HttpHeaders, which eventually implements
`Map<String, List<String>>`. It has explicit computed properties for
most of the common headers, with such property types as URI (Location)
and long (Last-Modified). Groovy, however, interprets
`headers.location` as a call to `headers.get('location')` and returns
a `List<String>` instead of `headers.getLocation()`.

This feature provides novel and evening-occupying results
(particularly as Eclipse's hover and autocomplete suggest the expected
behavior!).

== Proposal ==

Starting in Groovy 4, prioritize explicitly defined properties for
resolution ahead of `Map#get`.

If some object implementing Map actually defines properties, then it
seems to me a reasonable guess that that support is based on the
intended/expected usage patterns, and that users employing that object
would expect to have it available. In the case where clients
specifically want to use a lookup, get and `[]` are easily available.

--
Christopher Smith
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Change map access semantics in 4.0

Christopher Smith

Just to clarify, there is no issue
when using the full getter name for property getter, correct? E.g. map.isEmpty()

That's correct; explicitly invoking the methods always works. The current logic, though, leads to circumstances where

def v1 = value
obj.foo = v1
v1 != obj.foo

(among other pitfalls).