You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@groovy.apache.org by Cédric Champeau <ce...@gmail.com> on 2016/05/02 16:44:51 UTC

Automatic closure coercion and delegate

Hi guys,

I've been grumpy about this for a bit too long to keep it for myself, so
let me explain the issue :)

Imagine you have a Java method that accepts a SAM type:

interface Action<T> {
   void execute(T object)
}

class Person {
    String name
}

void configure(Action<Person> config) {
   config.execute(person)
}

then, you can call it in Groovy like this:

configure {
   it.name = 'Bob'
}

Whereas if we had a closure version, a nice and idiomatic way would be to
write:

configure {
   name = 'Bob'
}

Note that in the `Action` version, we have to prefix everything with "it.".

My wish is to make automatic closure coercion automatically set the
delegate to the first argument, if available, and the delegation strategy
to delegate first.

Basically, it is important to integrate with Java 8 style SAM types and
still benefit from a nicer Groovy DSL _without_ having to change the source
files. Typically, we don't have access to the JDK sources, so we have to
write:

def max =['Cedric','Jochen','Guillaume', 'Paul'].stream()
  .mapToInt { it.length() }
  .max()
  .orElse(0)

Where with this strategy we could use:

def max =['Cedric','Jochen','Guillaume', 'Paul'].stream()
  .mapToInt { length() }
  .max()
  .orElse(0)

Of course, it may look a bit superficial but it is super important for nice
DSLs like in Gradle. Typically, Gradle has a lot of domain objects that use
the `Action<T>` interface above. Those actions allow the user to configure
the domain objects typically from plugins written in Java (where you cannot
use a closure). Since the `Closure` equivalent methods are always the same
and that it's super simple to forget to implement one, Gradle chose to
_not_ implement the `Closure` versions. Instead, they are generated at
runtime, so the objects are decorated with one `Closure` method for each
`Action` one.

Unfortunately, this approach is defeated as soon as you want to use static
compilation: then, you have no choice but implementing the `Closure`
versions. This might be an option for Gradle (even though it would be very
tedious), but not for all cases (we could also do this using extension
methods, though, but really, you'd be doing this for _all_ domain objects).
I think I could write a code generator that takes all java classes and
generates an extension class with closure versions for all, also, but I'd
like to know first what you think of this idea...

Re: Automatic closure coercion and delegate

Posted by Guillaume Laforge <gl...@gmail.com>.
On Mon, May 2, 2016 at 5:25 PM, Joe Wolf <jo...@gmail.com> wrote:

> [...]
>
> [Hi, all. This is my first post to the list--been a happy Groovy user
> since version 1.5]
>

Welcome here and thanks for using Groovy since 1.5!!! :-)

Guillaume


>
> -Joe
>
>
> On Mon, May 2, 2016 at 10:56 AM, Guillaume Laforge <gl...@gmail.com>
> wrote:
>
>> +1
>>
>> On Mon, May 2, 2016 at 4:44 PM, Cédric Champeau <
>> cedric.champeau@gmail.com> wrote:
>>
>>> Hi guys,
>>>
>>> I've been grumpy about this for a bit too long to keep it for myself, so
>>> let me explain the issue :)
>>>
>>> Imagine you have a Java method that accepts a SAM type:
>>>
>>> interface Action<T> {
>>>    void execute(T object)
>>> }
>>>
>>> class Person {
>>>     String name
>>> }
>>>
>>> void configure(Action<Person> config) {
>>>    config.execute(person)
>>> }
>>>
>>> then, you can call it in Groovy like this:
>>>
>>> configure {
>>>    it.name = 'Bob'
>>> }
>>>
>>> Whereas if we had a closure version, a nice and idiomatic way would be
>>> to write:
>>>
>>> configure {
>>>    name = 'Bob'
>>> }
>>>
>>> Note that in the `Action` version, we have to prefix everything with
>>> "it.".
>>>
>>> My wish is to make automatic closure coercion automatically set the
>>> delegate to the first argument, if available, and the delegation strategy
>>> to delegate first.
>>>
>>> Basically, it is important to integrate with Java 8 style SAM types and
>>> still benefit from a nicer Groovy DSL _without_ having to change the source
>>> files. Typically, we don't have access to the JDK sources, so we have to
>>> write:
>>>
>>> def max =['Cedric','Jochen','Guillaume', 'Paul'].stream()
>>>   .mapToInt { it.length() }
>>>   .max()
>>>   .orElse(0)
>>>
>>> Where with this strategy we could use:
>>>
>>> def max =['Cedric','Jochen','Guillaume', 'Paul'].stream()
>>>   .mapToInt { length() }
>>>   .max()
>>>   .orElse(0)
>>>
>>> Of course, it may look a bit superficial but it is super important for
>>> nice DSLs like in Gradle. Typically, Gradle has a lot of domain objects
>>> that use the `Action<T>` interface above. Those actions allow the user to
>>> configure the domain objects typically from plugins written in Java (where
>>> you cannot use a closure). Since the `Closure` equivalent methods are
>>> always the same and that it's super simple to forget to implement one,
>>> Gradle chose to _not_ implement the `Closure` versions. Instead, they are
>>> generated at runtime, so the objects are decorated with one `Closure`
>>> method for each `Action` one.
>>>
>>> Unfortunately, this approach is defeated as soon as you want to use
>>> static compilation: then, you have no choice but implementing the `Closure`
>>> versions. This might be an option for Gradle (even though it would be very
>>> tedious), but not for all cases (we could also do this using extension
>>> methods, though, but really, you'd be doing this for _all_ domain objects).
>>> I think I could write a code generator that takes all java classes and
>>> generates an extension class with closure versions for all, also, but I'd
>>> like to know first what you think of this idea...
>>>
>>>
>>
>>
>> --
>> Guillaume Laforge
>> Apache Groovy committer & PMC Vice-President
>> Product Ninja & Advocate at Restlet <http://restlet.com>
>>
>> Blog: http://glaforge.appspot.com/
>> Social: @glaforge <http://twitter.com/glaforge> / Google+
>> <https://plus.google.com/u/0/114130972232398734985/posts>
>>
>
>


-- 
Guillaume Laforge
Apache Groovy committer & PMC Vice-President
Product Ninja & Advocate at Restlet <http://restlet.com>

Blog: http://glaforge.appspot.com/
Social: @glaforge <http://twitter.com/glaforge> / Google+
<https://plus.google.com/u/0/114130972232398734985/posts>

Re: Automatic closure coercion and delegate

Posted by Joe Wolf <jo...@gmail.com>.
+1

Would it be sensible/possible to add a Closure.FIRST_ARGUMENT resolve
strategy and include it in the default resolution chain? The 'it'-less
closure would behave as expected even without pre-assigning the delegate
(provided that length() was not defined by the delegate/owner). It'd still
probably be a good idea to automatically set the delegate anyways...just
throwing out some thoughts.

[Hi, all. This is my first post to the list--been a happy Groovy user since
version 1.5]

-Joe


On Mon, May 2, 2016 at 10:56 AM, Guillaume Laforge <gl...@gmail.com>
wrote:

> +1
>
> On Mon, May 2, 2016 at 4:44 PM, Cédric Champeau <cedric.champeau@gmail.com
> > wrote:
>
>> Hi guys,
>>
>> I've been grumpy about this for a bit too long to keep it for myself, so
>> let me explain the issue :)
>>
>> Imagine you have a Java method that accepts a SAM type:
>>
>> interface Action<T> {
>>    void execute(T object)
>> }
>>
>> class Person {
>>     String name
>> }
>>
>> void configure(Action<Person> config) {
>>    config.execute(person)
>> }
>>
>> then, you can call it in Groovy like this:
>>
>> configure {
>>    it.name = 'Bob'
>> }
>>
>> Whereas if we had a closure version, a nice and idiomatic way would be to
>> write:
>>
>> configure {
>>    name = 'Bob'
>> }
>>
>> Note that in the `Action` version, we have to prefix everything with
>> "it.".
>>
>> My wish is to make automatic closure coercion automatically set the
>> delegate to the first argument, if available, and the delegation strategy
>> to delegate first.
>>
>> Basically, it is important to integrate with Java 8 style SAM types and
>> still benefit from a nicer Groovy DSL _without_ having to change the source
>> files. Typically, we don't have access to the JDK sources, so we have to
>> write:
>>
>> def max =['Cedric','Jochen','Guillaume', 'Paul'].stream()
>>   .mapToInt { it.length() }
>>   .max()
>>   .orElse(0)
>>
>> Where with this strategy we could use:
>>
>> def max =['Cedric','Jochen','Guillaume', 'Paul'].stream()
>>   .mapToInt { length() }
>>   .max()
>>   .orElse(0)
>>
>> Of course, it may look a bit superficial but it is super important for
>> nice DSLs like in Gradle. Typically, Gradle has a lot of domain objects
>> that use the `Action<T>` interface above. Those actions allow the user to
>> configure the domain objects typically from plugins written in Java (where
>> you cannot use a closure). Since the `Closure` equivalent methods are
>> always the same and that it's super simple to forget to implement one,
>> Gradle chose to _not_ implement the `Closure` versions. Instead, they are
>> generated at runtime, so the objects are decorated with one `Closure`
>> method for each `Action` one.
>>
>> Unfortunately, this approach is defeated as soon as you want to use
>> static compilation: then, you have no choice but implementing the `Closure`
>> versions. This might be an option for Gradle (even though it would be very
>> tedious), but not for all cases (we could also do this using extension
>> methods, though, but really, you'd be doing this for _all_ domain objects).
>> I think I could write a code generator that takes all java classes and
>> generates an extension class with closure versions for all, also, but I'd
>> like to know first what you think of this idea...
>>
>>
>
>
> --
> Guillaume Laforge
> Apache Groovy committer & PMC Vice-President
> Product Ninja & Advocate at Restlet <http://restlet.com>
>
> Blog: http://glaforge.appspot.com/
> Social: @glaforge <http://twitter.com/glaforge> / Google+
> <https://plus.google.com/u/0/114130972232398734985/posts>
>

Re: Automatic closure coercion and delegate

Posted by Guillaume Laforge <gl...@gmail.com>.
+1

On Mon, May 2, 2016 at 4:44 PM, Cédric Champeau <ce...@gmail.com>
wrote:

> Hi guys,
>
> I've been grumpy about this for a bit too long to keep it for myself, so
> let me explain the issue :)
>
> Imagine you have a Java method that accepts a SAM type:
>
> interface Action<T> {
>    void execute(T object)
> }
>
> class Person {
>     String name
> }
>
> void configure(Action<Person> config) {
>    config.execute(person)
> }
>
> then, you can call it in Groovy like this:
>
> configure {
>    it.name = 'Bob'
> }
>
> Whereas if we had a closure version, a nice and idiomatic way would be to
> write:
>
> configure {
>    name = 'Bob'
> }
>
> Note that in the `Action` version, we have to prefix everything with "it.".
>
> My wish is to make automatic closure coercion automatically set the
> delegate to the first argument, if available, and the delegation strategy
> to delegate first.
>
> Basically, it is important to integrate with Java 8 style SAM types and
> still benefit from a nicer Groovy DSL _without_ having to change the source
> files. Typically, we don't have access to the JDK sources, so we have to
> write:
>
> def max =['Cedric','Jochen','Guillaume', 'Paul'].stream()
>   .mapToInt { it.length() }
>   .max()
>   .orElse(0)
>
> Where with this strategy we could use:
>
> def max =['Cedric','Jochen','Guillaume', 'Paul'].stream()
>   .mapToInt { length() }
>   .max()
>   .orElse(0)
>
> Of course, it may look a bit superficial but it is super important for
> nice DSLs like in Gradle. Typically, Gradle has a lot of domain objects
> that use the `Action<T>` interface above. Those actions allow the user to
> configure the domain objects typically from plugins written in Java (where
> you cannot use a closure). Since the `Closure` equivalent methods are
> always the same and that it's super simple to forget to implement one,
> Gradle chose to _not_ implement the `Closure` versions. Instead, they are
> generated at runtime, so the objects are decorated with one `Closure`
> method for each `Action` one.
>
> Unfortunately, this approach is defeated as soon as you want to use static
> compilation: then, you have no choice but implementing the `Closure`
> versions. This might be an option for Gradle (even though it would be very
> tedious), but not for all cases (we could also do this using extension
> methods, though, but really, you'd be doing this for _all_ domain objects).
> I think I could write a code generator that takes all java classes and
> generates an extension class with closure versions for all, also, but I'd
> like to know first what you think of this idea...
>
>


-- 
Guillaume Laforge
Apache Groovy committer & PMC Vice-President
Product Ninja & Advocate at Restlet <http://restlet.com>

Blog: http://glaforge.appspot.com/
Social: @glaforge <http://twitter.com/glaforge> / Google+
<https://plus.google.com/u/0/114130972232398734985/posts>

Re: Automatic closure coercion and delegate

Posted by Graeme Rocher <gr...@gmail.com>.
+1 this seems like a great idea.

Graeme
On Wed, May 18, 2016 at 9:11 AM, Jochen Theodorou <bl...@gmx.org> wrote:
On 18.05.2016 08:46, Peter Ledbrook wrote:
> I'm not even sure there's a right answer to this. I guess I would expect
> local variables defined outside of the closure to be accessible within
> it. Would that work with DELEGATE_ONLY?

yes, static scoping is done by the compiler, before any runtime can
fiddle around with redirected calls using the delegation strategy

bye Jochen

Re: Automatic closure coercion and delegate

Posted by Jochen Theodorou <bl...@gmx.org>.
On 18.05.2016 08:46, Peter Ledbrook wrote:
> I'm not even sure there's a right answer to this. I guess I would expect
> local variables defined outside of the closure to be accessible within
> it. Would that work with DELEGATE_ONLY?

yes, static scoping is done by the compiler, before any runtime can 
fiddle around with redirected calls using the delegation strategy

bye Jochen


Re: Automatic closure coercion and delegate

Posted by Peter Ledbrook <pe...@cacoethes.co.uk>.
I'm not even sure there's a right answer to this. I guess I would expect
local variables defined outside of the closure to be accessible within it.
Would that work with DELEGATE_ONLY?

Peter

On Tue, 17 May 2016 at 15:19 Cédric Champeau <ce...@gmail.com>
wrote:

> As an update, I have made an experiment to automatically generate
> extension methods that do this. It kind of works and reduces the immediate
> need for this. Especially in the context of static compilation. The issue
> is always what to choose as the default delegation strategy. While delegate
> first is often the one used in DSL, I could also see benefit in using
> delegate only in the context of statically generated code...
>
> 2016-05-04 7:46 GMT+02:00 Mario Garcia <ma...@gmail.com>:
>
>> +1
>>
>> 2016-05-03 10:29 GMT+02:00 Jochen Theodorou <bl...@gmx.org>:
>>
>>> On 03.05.2016 08:26, Cédric Champeau wrote:
>>> [...]
>>>
>>>> repositories { // Action<? super RepositoryHander
>>>>     maven { Action<? super MavenRepository>
>>>>          url '....'
>>>>     }
>>>> }
>>>>
>>>
>>> I see... I would feel much better if this was done by a special
>>> interface, coming from Groovy... maybe even a trait. But I guess this is
>>> not really an option.
>>>
>>> [...]
>>>
>>> Doing the same for abstract classes should be straightforward. For
>>>> static compilation, it's going to be more complicated and probably
>>>> requires transparently invoking a configurer (like Gradle does).
>>>>
>>>
>>> the proxy generation will work the same, I guess you are talking about
>>> the direct method calls inside the closure as well es letting it pass
>>> static compilation. But I still don't understand what you mean by configurer
>>>
>>> bye Jochen
>>>
>>>
>>>
>>
>

Re: Automatic closure coercion and delegate

Posted by Cédric Champeau <ce...@gmail.com>.
As an update, I have made an experiment to automatically generate extension
methods that do this. It kind of works and reduces the immediate need for
this. Especially in the context of static compilation. The issue is always
what to choose as the default delegation strategy. While delegate first is
often the one used in DSL, I could also see benefit in using delegate only
in the context of statically generated code...

2016-05-04 7:46 GMT+02:00 Mario Garcia <ma...@gmail.com>:

> +1
>
> 2016-05-03 10:29 GMT+02:00 Jochen Theodorou <bl...@gmx.org>:
>
>> On 03.05.2016 08:26, Cédric Champeau wrote:
>> [...]
>>
>>> repositories { // Action<? super RepositoryHander
>>>     maven { Action<? super MavenRepository>
>>>          url '....'
>>>     }
>>> }
>>>
>>
>> I see... I would feel much better if this was done by a special
>> interface, coming from Groovy... maybe even a trait. But I guess this is
>> not really an option.
>>
>> [...]
>>
>> Doing the same for abstract classes should be straightforward. For
>>> static compilation, it's going to be more complicated and probably
>>> requires transparently invoking a configurer (like Gradle does).
>>>
>>
>> the proxy generation will work the same, I guess you are talking about
>> the direct method calls inside the closure as well es letting it pass
>> static compilation. But I still don't understand what you mean by configurer
>>
>> bye Jochen
>>
>>
>>
>

Re: Automatic closure coercion and delegate

Posted by Mario Garcia <ma...@gmail.com>.
+1

2016-05-03 10:29 GMT+02:00 Jochen Theodorou <bl...@gmx.org>:

> On 03.05.2016 08:26, Cédric Champeau wrote:
> [...]
>
>> repositories { // Action<? super RepositoryHander
>>     maven { Action<? super MavenRepository>
>>          url '....'
>>     }
>> }
>>
>
> I see... I would feel much better if this was done by a special interface,
> coming from Groovy... maybe even a trait. But I guess this is not really an
> option.
>
> [...]
>
> Doing the same for abstract classes should be straightforward. For
>> static compilation, it's going to be more complicated and probably
>> requires transparently invoking a configurer (like Gradle does).
>>
>
> the proxy generation will work the same, I guess you are talking about the
> direct method calls inside the closure as well es letting it pass static
> compilation. But I still don't understand what you mean by configurer
>
> bye Jochen
>
>
>

Re: Automatic closure coercion and delegate

Posted by Jochen Theodorou <bl...@gmx.org>.
On 03.05.2016 08:26, C�dric Champeau wrote:
[...]
> repositories { // Action<? super RepositoryHander
>     maven { Action<? super MavenRepository>
>          url '....'
>     }
> }

I see... I would feel much better if this was done by a special 
interface, coming from Groovy... maybe even a trait. But I guess this is 
not really an option.

[...]

> Doing the same for abstract classes should be straightforward. For
> static compilation, it's going to be more complicated and probably
> requires transparently invoking a configurer (like Gradle does).

the proxy generation will work the same, I guess you are talking about 
the direct method calls inside the closure as well es letting it pass 
static compilation. But I still don't understand what you mean by configurer

bye Jochen



Re: Automatic closure coercion and delegate

Posted by Cédric Champeau <ce...@gmail.com>.
2016-05-02 18:11 GMT+02:00 Jochen Theodorou <bl...@gmx.org>:

> On 02.05.2016 16:44, Cédric Champeau wrote:
> [...]
>
>> Of course, it may look a bit superficial but it is super important for
>> nice DSLs like in Gradle.
>>
>
> could you give an example of a more complex closure usage?
>

Sure, basically you can have that kind of configuration:

dependencies {
   compile '...'
}

(the method is dependencies(Action<? super DependencyContainer>, and we
generate a Closure version at runtime) or:

repositories { // Action<? super RepositoryHander
   maven { Action<? super MavenRepository>
        url '....'
   }
}


> We should be also aware that this change may break code, since it is
> semantic change and a local method of the same name will no longer be
> called if it exists on the delegate. A functional interface is after all
> not something that came really to exist with java8 only, Callable and
> Runnable are examples that existed before and work with Closure already.
>
> Implementation wise to have something like
>
> void configure(Action<Person> config) {
>> config.execute(person)
>> }
>>
>
> working we need to set the delegate to person, but we don´t know person
> before the method invocation. This means the proxy for the Action
> delegating to the closure config represents must also make the delegation
> setter call.
>
> And there is also the problem of what we do if Action contains default
> methods - I do not consider our current solution as appropriate anymore.
> But of course that is not essential for the idea at hand.
>
>
I have a patch that does the work for functional interfaces already:

public static Object coerceToSAM(Closure argument, Method method,
Class clazz, boolean isInterface) {
    if (argument!=null && clazz.isAssignableFrom(argument.getClass())) {
        return argument;
    }
    if (isInterface) {
        if (Traits.isTrait(clazz)) {
            Map<String,Closure> impl = Collections.singletonMap(
                    method.getName(),
                    argument
            );
            return
ProxyGenerator.INSTANCE.instantiateAggregate(impl,Collections.singletonList(clazz));
        }
        return Proxy.newProxyInstance(
                clazz.getClassLoader(),
                new Class[]{clazz},
                new SAMClosure(argument));
    } else {
        Map<String, Object> m = new HashMap<String,Object>();
        m.put(method.getName(), argument);
        return ProxyGenerator.INSTANCE.
                instantiateAggregateFromBaseClass(m, clazz);
    }
}

private static class SAMClosure extends ConvertedClosure {
    public SAMClosure(final Closure closure) {
        super(closure);
    }

    @Override
    public Object invokeCustom(final Object proxy, final Method
method, final Object[] args) throws Throwable {
        if (args!=null && args.length>0) {
            Closure delegate = (Closure) getDelegate();
            delegate.setResolveStrategy(Closure.DELEGATE_FIRST);
            delegate.setDelegate(args[0]);
        }
        return super.invokeCustom(proxy, method, args);
    }
}


Doing the same for abstract classes should be straightforward. For static
compilation, it's going to be more complicated and probably requires
transparently invoking a configurer (like Gradle does).

>
> bye Jochen
>

Re: Automatic closure coercion and delegate

Posted by Jochen Theodorou <bl...@gmx.org>.
On 02.05.2016 16:44, C�dric Champeau wrote:
[...]
> Of course, it may look a bit superficial but it is super important for
> nice DSLs like in Gradle.

could you give an example of a more complex closure usage?

We should be also aware that this change may break code, since it is 
semantic change and a local method of the same name will no longer be 
called if it exists on the delegate. A functional interface is after all 
not something that came really to exist with java8 only, Callable and 
Runnable are examples that existed before and work with Closure already.

Implementation wise to have something like

> void configure(Action<Person> config) {
> config.execute(person)
> }

working we need to set the delegate to person, but we don�t know person 
before the method invocation. This means the proxy for the Action 
delegating to the closure config represents must also make the 
delegation setter call.

And there is also the problem of what we do if Action contains default 
methods - I do not consider our current solution as appropriate anymore. 
But of course that is not essential for the idea at hand.


bye Jochen