Re: Improving named-argument support

2018-01-16 Thread MG
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 "
}

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 args) { ... }

or

@NamedParams([
  @NamedParam("password"),
  @NamedParam(value = "user", type = "String", required = true)])
Sql newInstance(Map 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

2018-01-16 Thread Nathan Harvey
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


Re: Executing dynamic DSL command

2018-01-16 Thread Mohan Radhakrishnan
I mean that this works if the DSL command is hard-coded. I was trying to
pass it into the shell.

Am I allowed to pass it into the shell ? This evaluation is generic for
many such fluent commands.

def evaluatexpath( String dsl ){
def binding = new Binding(
dxp: new DynamicXPath(),
DslCommand: dsl
)

def shell = new GroovyShell(this.class.classLoader, binding)

shell.evaluate '''
@BaseScript(com.automation.script.AxesBaseScript)
import groovy.transform.BaseScript
bootStrap "//*[@id='pane']"
'''
}

Mohan


On 16 January 2018 at 19:06, Mohan Radhakrishnan <
radhakrishnan.mo...@gmail.com> wrote:

> Hi,
>
>   I am reading the book 'Groovy in Action' and this code is based on
> that.
>
> My base script stores 'DynamicXPath' like this.
>
> abstract class AxesBaseScript extends Script {
>
> public DynamicXPath bootStrap(String initialPath ){
> this.binding.dxp.bootStrap initialPath
> }
> }
>
> And here I want to execute a dynamic DSL command 'dsl '
>
> by storing it in the binding as 'DslCommand'.
>
> def evaluatexpath( String dsl ){
> def binding = new Binding(
> dsp: new DynamicXPath(),
> DslCommand: dsl
> )
>
> def shell = new GroovyShell(this.class.classLoader, binding)
>
> shell.evaluate '''
> @BaseScript(com.automation.script.AxesBaseScript)
> import groovy.transform.BaseScript
> DslCommand
> '''
> }
>
> Looks like the DSL string I pass 'bootStrap somestring' is returned as it
> is as a string by evaluate.
>
> Can I not execute 'bootStrap somestring' ?
>
> The relevant section in the book is '*Listing 19.12 Using a custom base
> script class'*
>
> *My '*DslCommand' replaces 'move left' which you see at the end of the
> listing.
> I just want to pass my DSL commands to the evaluate method as parameters
> through the binding.
>
>
> Thanks,
> Mohan
>


Executing dynamic DSL command

2018-01-16 Thread Mohan Radhakrishnan
Hi,

  I am reading the book 'Groovy in Action' and this code is based on
that.

My base script stores 'DynamicXPath' like this.

abstract class AxesBaseScript extends Script {

public DynamicXPath bootStrap(String initialPath ){
this.binding.dxp.bootStrap initialPath
}
}

And here I want to execute a dynamic DSL command 'dsl '

by storing it in the binding as 'DslCommand'.

def evaluatexpath( String dsl ){
def binding = new Binding(
dsp: new DynamicXPath(),
DslCommand: dsl
)

def shell = new GroovyShell(this.class.classLoader, binding)

shell.evaluate '''
@BaseScript(com.automation.script.AxesBaseScript)
import groovy.transform.BaseScript
DslCommand
'''
}

Looks like the DSL string I pass 'bootStrap somestring' is returned as it
is as a string by evaluate.

Can I not execute 'bootStrap somestring' ?

The relevant section in the book is '*Listing 19.12 Using a custom base
script class'*

*My '*DslCommand' replaces 'move left' which you see at the end of the
listing.
I just want to pass my DSL commands to the evaluate method as parameters
through the binding.


Thanks,
Mohan


Re: Improving named-argument support

2018-01-16 Thread Daniil Ovchinnikov
> @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)
> }

Do you mean there will be additional method while original method will be left 
as is?

In case of named parameter with default value, there will be tree methods:

def foo(a, @NamedParam b = 42, @NamedParam c) { a+b+c }  // source method

def foo(Map _it, a) { this(a, _it.b, _it.c) } // generated from @NamedParam
def foo(a, c) { this(a, 42, c) }  // generated from default value
def foo(a, b, c) { a+b+c }// actual method

Will default be passed if I invoke code with foo(“value for a”, c: 13) ?

How ‘required’ is supposed to work with default parameters?
Why not leverage default params instead of introducing ‘required’? I.e. all 
named params are required by default.

—

Daniil Ovchinnikov
Software Developer
JetBrains
jetbrains.com
“Drive to develop”