You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@groovy.apache.org by Paul King <pa...@asert.com.au> on 2018/01/16 05:54:02 UTC

Improving named-argument support

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"
}

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.

Re: Improving named-argument support

Posted by Paul King <pa...@asert.com.au>.
Yes, we have spoken about providing named arguments as a language feature
before. It doesn't rule out the need for other options though. I have put
some comments in the issue.

On Thu, Jan 18, 2018 at 7:37 AM, Nathan Harvey <na...@gmail.com>
wrote:

> MG's approach is the one I favor, although I am not sure I like using = in
> the calling syntax; the colon makes more sense to me. Small detail, though.
>
>
>
> --
> Sent from: http://groovy.329449.n5.nabble.com/Groovy-Users-f329450.html
>

Re: Improving named-argument support

Posted by Nathan Harvey <na...@gmail.com>.
MG's approach is the one I favor, although I am not sure I like using = in
the calling syntax; the colon makes more sense to me. Small detail, though.



--
Sent from: http://groovy.329449.n5.nabble.com/Groovy-Users-f329450.html

Re: Improving named-argument support

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


Re: Improving named-argument support

Posted by Paul King <pa...@asert.com.au>.
Response below.

On Wed, Jan 17, 2018 at 7:44 AM, Nathan Harvey <na...@gmail.com>
wrote:

> Paul, I am very much in favor of this idea, but I do not like the
> execution.
> The need for those annotations makes it quite verbose and it seems a bit
> too
> complex. I agree with Daniil that having the "required" flag is also
> unnecessary. @NamedParam seems like it should be assumed. I understand the
> use case of @NamedDelegate but again, it adds complexity. Ideally you would
> only need one annotation to the method for a sensible default case.
>

 I imagine a very common use case would be to just have the single
@NamedVariant
annotation on the method/constructor and nothing else. The other
annotations are
just used to cover the more complex use cases that have been spoken about in
various discussions. If you don't need them, don't use them. There are many
examples
even in the Groovy codebase where auto `required` wouldn't be appropriate,
e.g. the
Sql.newIstance case. There are examples of using that with various
combinations:
(url, properties), (url, driverClassName), (url, username, password,
driver), etc.
There are other examples where required would make sense.

Re: Improving named-argument support

Posted by Nathan Harvey <na...@gmail.com>.
Paul, I am very much in favor of this idea, but I do not like the execution.
The need for those annotations makes it quite verbose and it seems a bit too
complex. I agree with Daniil that having the "required" flag is also
unnecessary. @NamedParam seems like it should be assumed. I understand the
use case of @NamedDelegate but again, it adds complexity. Ideally you would
only need one annotation to the method for a sensible default case.



--
Sent from: http://groovy.329449.n5.nabble.com/Groovy-Users-f329450.html