groovy-users mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From MG <mg...@arscreat.com>
Subject Re: Improving named-argument support
Date Tue, 16 Jan 2018 23:20:31 GMT
I have created a Jira issue about Groovy non-map based named parameter 
support:
https://issues.apache.org/jira/browse/GROOVY-8451

On 16.01.2018 06:54, Paul King wrote:
>
> We have a number of open issues around improving named-argument support.
> I am currently doing a spike around one AST transformation and one 
> metadata
> annotation for the type checker. These relate to various issues and 
> pull requests
> that already contain some history:
>
> [1] https://issues.apache.org/jira/browse/GROOVY-7956
> [2] https://issues.apache.org/jira/browse/GROOVY-7632
> [3] https://github.com/bsideup/GroovyMapArguments
>
> My current spike borrows code from above with some slight renaming.
> It currently has a @NamedVariant AST transform targetted for 2.5 if I 
> get it finished soon.
> This can be placed on any method or constructor and produces a variant
> with a Map as the first parameter that complies with Groovy's existing
> named-argument approach. The method looks for any parameter annotated
> with @NamedParam or @NamedDelegate. These are conceptually "pulled out"
> of the generated method's signature and passed along via the first 
> argument map.
> Any parameter annotated with @NamedParam is assumed to correspond to a 
> single key
> for the map. For any parameter annotated with @NamedDelegate, the 
> property names
> of the type of that argument are assumed to be the keys.
>
> Examples include:
>
> @NamedVariant
> def foo(a, @NamedParam String b, c, @NamedParam(required=true) d) {
>   println "$a $b $c $d"
> }
>
> which produces a method like this:
>
> def foo(Map _it, a, c) {
>   assert _it.containsKey('d')
>   this(a, _it.b, c, _it.d)
> }
>
> If we have this class:
>
> class Animal {
>     String type
>     String name
> }
>
> Then this definition:
>
> @NamedVariant
> def describe(@NamedDelegate Animal animal) {
>     "${animal.type.toUpperCase()}:$animal.name <http://animal.name>"
> }
>
> produces an additional method like this:
>
> def describe(Map __namedArgs) {
>     this.describe((( __namedArgs ) as Animal))
> }
>
> which could be called like this:
>
> assert describe(type: 'Dog', name: 'Rover') == 'DOG:Rover'
>
> Currently if no @NamedXXX annotations are found, @NamedDelegate
> is assumed for the first argument (so we could have left it out in the 
> example above).
> Currently any number of @NamedParam and @NamedDelegate annotations
> can be used but keys can't be duplicated.
>
> A more elaborate example is as follows:
>
> // another domain class
> class Color {
>     Integer r, g, b
> }
>
> @NamedVariant
> def describe(@NamedDelegate Animal animal,
>              @NamedDelegate Color color,
>              @NamedParam('dob') Date born) { ... }
>
> produces (approximately since I am ignoring missing keys):
>
> def describe(Map __namedArgs) {
>     Map __colorArgs = [:]
>     __colorArgs.r = __namedArgs.r
>     __colorArgs.g = __namedArgs.g
>     __colorArgs.b = __namedArgs.b
>     Map __animalArgs = [:]
>     __animalArgs.type = __namedArgs.type
>     __animalArgs.name = __namedArgs.name
>     this.describe(__animalArgs  as Animal, __colorArgs as Color, 
> __namedArgs.dob)
> }
>
> This has little impact on type checking since normal type checking 
> will occur on
> the non-Map variant which is still retained. We can do a tiny bit more 
> if we keep
> the annotation around and check the required flag but that is a minor 
> discussion
> point. We could also generate some additional metadata annotation(s) 
> as per
> subsequent discussion if needed but I'll defer that discussion for now.
>
> Over and above the @NamedVariant annotation, I am also suggesting a
> way to improve type checking on Map-based methods where the @NamedVariant
> might not be possible, e.g. Java classes or existing classes which 
> can't easily
> be changed. It would look like the following (again heavily based on 
> above references):
>
> @NamedParams(typeHint = SqlTypeHelper)
> Sql newInstance(Map<String, Object> args) { ... }
>
> or
>
> @NamedParams([
>   @NamedParam("password"),
>   @NamedParam(value = "user", type = "String", required = true)])
> Sql newInstance(Map<String, Object> args) { ... }
>
> In the first case the property names and types from the typeHint class 
> are used
> for improved type checking. In the second case, the information is 
> embedded in
> the nested annotations.
>
> Any thoughts or early comments while I continue on the spike(s)?
> More detailed comments can wait for the spike(s) so long as this doesn't
> seem way off what people would like to see. It's currently one spike
> but I'll probably split into two before making the PRs.
>
> Cheers, Paul.
>


Mime
View raw message