You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@groovy.apache.org by Andres Almiray <aa...@gmail.com> on 2017/01/18 10:59:33 UTC

SAM type closure coercion

Hello everyone,

Just yesterday Greg L. Turnquist blogged about an usage pattern of
Spinnaker, Cloud Foundry, and Groovy. See
http://greglturnquist.com/2017/01/reactively-talking-to-cloud-foundry-with-groovy.html

Basically he complains that Groovy can't coerce a Closure to a given SAM
type. In his own words

"Groovy has this nice feature where it can coerce objects. However, with
all the overloading, Groovy gets lost and can’t tell which TupleUtils
function to target."

Now I know that based on historical reasons Groovy did not coerce closures
into SAMs until very "recently" (was it 2.2?). We also gained @DelegatesTo
in order to supply additional hints to the compiler (@CompileStatic and
@TypeChecked) and IDEs. Despite all this Groovy does not offer a "clean"
solution to automatically coerce a closure into a SAM.

Take for example the following Java code:

----
public interface Function1 { void call(String arg0); }

public interface Function2 { void call(String arg0, String arg1); }

import groovy.lang.DelegatesTo;
public class API {
    public void doit(@DelegatesTo Function1 func) {
        System.out.println("Invoking "+ func.toString());
        func.call("arg0");
    }

    public void doit(@DelegatesTo Function2 func) {
        System.out.println("Invoking "+ func.toString());
        func.call("arg0", "arg1");
    }
}
-----

Invoking an instance of API from Groovy

----
class Main {
    static void main(String[] args) {
        API api = new API()
        api.doit({ String arg0 -> println "Received $arg0" })
        api.doit({ String arg0, String arg1 -> println "Received $arg0
$arg1" })
    }
}
----

Results in a runtime exception such as

Exception in thread "main" groovy.lang.GroovyRuntimeException: Ambiguous
method overloading for method sample.API#doit.
Cannot resolve which method to invoke for [class
sample.Main$_main_closure1] due to overlapping prototypes between:
        [interface sample.Function1]
        [interface sample.Function2]
        at
groovy.lang.MetaClassImpl.chooseMostSpecificParams(MetaClassImpl.java:3263)
        at
groovy.lang.MetaClassImpl.chooseMethodInternal(MetaClassImpl.java:3216)
        at groovy.lang.MetaClassImpl.chooseMethod(MetaClassImpl.java:3159)
        at
groovy.lang.MetaClassImpl.getMethodWithCachingInternal(MetaClassImpl.java:1336)
        at
groovy.lang.MetaClassImpl.createPojoCallSite(MetaClassImpl.java:3391)
        at
org.codehaus.groovy.runtime.callsite.CallSiteArray.createPojoSite(CallSiteArray.java:132)
        at
org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallSite(CallSiteArray.java:166)
        at
org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
        at
org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
        at
org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
        at sample.Main.main(Main.groovy:6)

Activating static type checking yields

/private/tmp/foo/src/main/groovy/sample/Main.groovy: 7: [Static type
checking] - Reference to method is ambiguous. Cannot choose between [void
sample.API#doit(sample.Function1), void sample.API#doit(sample.Function2)]
 @ line 7, column 9.
           api.doit({ String arg0 -> println "Received $arg0" })
           ^

/private/tmp/foo/src/main/groovy/sample/Main.groovy: 8: [Static type
checking] - Reference to method is ambiguous. Cannot choose between [void
sample.API#doit(sample.Function1), void sample.API#doit(sample.Function2)]
 @ line 8, column 9.
           api.doit({ String arg0, String arg1 -> println "Received $arg0
$arg1" })

Clearly Groovy requires some hints to determine the first closure must be
coerced to Function1 and the second to Function2. Not even @DelegatesTo
helps in this case.

The current "workaround" (from a Java dev POV anyway) is to sprinkle the
code with usages of the 'as' keyword to explicitly coerce a closure into a
target type. And this is exactly where the Java interop history breaks due
to Java 8, because as we know Java 8 lambda expressions are automatically
coerced into a matching SAM type.

Does the parrot parser offer an alternative to this problem?

Can the compiler be aware of additional arg/type information provided by
the Closure to figure out the right SAM type to use?

Cheers,
Andres

-------------------------------------------
Java Champion; Groovy Enthusiast
http://jroller.com/aalmiray
http://www.linkedin.com/in/aalmiray
--
What goes up, must come down. Ask any system administrator.
There are 10 types of people in the world: Those who understand binary, and
those who don't.
To understand recursion, we must first understand recursion.

Re: SAM type closure coercion

Posted by Jochen Theodorou <bl...@gmx.org>.
ok, I see... we currently do not make use of that information, but 
could. Only trouble is that we will be "less expressive" than Java for 
these cases.

bye Jochen

On 18.01.2017 14:39, Andres Almiray wrote:
> I meant that { String arg0, int arg1 -> } gives us arity and types,
> whereas { arg0, arg1 -> } only gives arity.
> Also compare { List arg0, int arg1 -> } vs { List<String> arg0, int arg1
> -> }, the former should be doable whereas the latter will prove difficult.
>
> This is what I meant by plain typed arguments. I explained myself badly
> and left out "generics" from the previous message.
>
> Cheers,
> Andres
>
> -------------------------------------------
> Java Champion; Groovy Enthusiast
> http://jroller.com/aalmiray
> http://www.linkedin.com/in/aalmiray
> --
> What goes up, must come down. Ask any system administrator.
> There are 10 types of people in the world: Those who understand binary,
> and those who don't.
> To understand recursion, we must first understand recursion.
>
> On Wed, Jan 18, 2017 at 2:22 PM, Jochen Theodorou <blackdrag@gmx.org
> <ma...@gmx.org>> wrote:
>
>
>     On 18.01.2017 14:09, Andres Almiray wrote:
>
>         Agreed.
>
>         I almost forgot about the special arity case of defining a
>         closure as {
>         /* do something */ } as it can be called with either 0 or 1
>         arguments,
>         where as { -> } accepts no arguments and { x -> } takes exactly one
>         argument.
>
>         Would it a a good compromise to support plain typed arguments
>         out of the
>         box, that is { String arg0, int arg1 -> } vs { arg0, arg1 -> } ?
>
>
>     nothing prevents you from writing { String arg0, int arg1 -> }
>     today. And yes, we can make that this helps.
>
>     bye Jochen
>
>

Re: SAM type closure coercion

Posted by Andres Almiray <aa...@gmail.com>.
I meant that { String arg0, int arg1 -> } gives us arity and types, whereas {
arg0, arg1 -> } only gives arity.
Also compare { List arg0, int arg1 -> } vs { List<String> arg0, int arg1 ->
}, the former should be doable whereas the latter will prove difficult.

This is what I meant by plain typed arguments. I explained myself badly and
left out "generics" from the previous message.

Cheers,
Andres

-------------------------------------------
Java Champion; Groovy Enthusiast
http://jroller.com/aalmiray
http://www.linkedin.com/in/aalmiray
--
What goes up, must come down. Ask any system administrator.
There are 10 types of people in the world: Those who understand binary, and
those who don't.
To understand recursion, we must first understand recursion.

On Wed, Jan 18, 2017 at 2:22 PM, Jochen Theodorou <bl...@gmx.org> wrote:

>
> On 18.01.2017 14:09, Andres Almiray wrote:
>
>> Agreed.
>>
>> I almost forgot about the special arity case of defining a closure as {
>> /* do something */ } as it can be called with either 0 or 1 arguments,
>> where as { -> } accepts no arguments and { x -> } takes exactly one
>> argument.
>>
>> Would it a a good compromise to support plain typed arguments out of the
>> box, that is { String arg0, int arg1 -> } vs { arg0, arg1 -> } ?
>>
>
> nothing prevents you from writing { String arg0, int arg1 -> } today. And
> yes, we can make that this helps.
>
> bye Jochen
>

Re: SAM type closure coercion

Posted by Jochen Theodorou <bl...@gmx.org>.
On 18.01.2017 14:09, Andres Almiray wrote:
> Agreed.
>
> I almost forgot about the special arity case of defining a closure as {
> /* do something */ } as it can be called with either 0 or 1 arguments,
> where as { -> } accepts no arguments and { x -> } takes exactly one
> argument.
>
> Would it a a good compromise to support plain typed arguments out of the
> box, that is { String arg0, int arg1 -> } vs { arg0, arg1 -> } ?

nothing prevents you from writing { String arg0, int arg1 -> } today. 
And yes, we can make that this helps.

bye Jochen

Re: SAM type closure coercion

Posted by Andres Almiray <aa...@gmail.com>.
Agreed.

I almost forgot about the special arity case of defining a closure as { /*
do something */ } as it can be called with either 0 or 1 arguments, where
as { -> } accepts no arguments and { x -> } takes exactly one argument.

Would it a a good compromise to support plain typed arguments out of the
box, that is { String arg0, int arg1 -> } vs { arg0, arg1 -> } ?

Cheers,
Andres

-------------------------------------------
Java Champion; Groovy Enthusiast
http://jroller.com/aalmiray
http://www.linkedin.com/in/aalmiray
--
What goes up, must come down. Ask any system administrator.
There are 10 types of people in the world: Those who understand binary, and
those who don't.
To understand recursion, we must first understand recursion.

On Wed, Jan 18, 2017 at 1:52 PM, Jochen Theodorou <bl...@gmx.org> wrote:

>
>
> On 18.01.2017 12:33, Remi Forax wrote:
>
>> I agree with Jesper,
>> solving all the cases is an uncanny valley.
>>
>> The JEP 302 [1] has a good introduction on what javac does or not in
>> case of overloading
>> (and what currently does not work but is expected to work with java 10).
>>
>> Also the warning proposed by Jesper exists in javac under the name
>> 'overloads'.
>>
>> cheers,
>> Rémi
>>
>> [1] http://openjdk.java.net/jeps/302
>>
>
> What Groovy currently does is look if the parameter type is a SAM type and
> then mark this as a potential match with conversion. It does not take arity
> into account, neither generics, nor the returntype of the "lambda".
>
> arity is a problem. {1} is a Groovy Closure with what arity? This can take
> one optional argument. {->1} is clear to take none and {x->1} is clear to
> take one argument. If we say you can use the shorter style, but then you
> may get into trouble, this is fine.
>
> Then there is of course not only the arity, but also the types of the
> parameters. {x->1} is an object taking Closure in Groovy. This is different
> from lambdas where the type of x may be defined by the SAM type we match
> against. So for example if I have foo(x->1+x>10) and there is a
> Predicate<String> as well as a Function<Integer,Boolean>, then of course
> the Function is supposed to be taken, but javac can try to first match
> against Predicate and if that does not work try to match against Function.
> It can see that for Integer, there is a plus method through unboxing, thus
> Function can work and if there are no further candidates the method
> selection is unambiguous, thus can be completed and no compilation error
> will happen. In dynamic Groovy we only see x of type Object. If there is a
> plus method is decided at runtime. But as such we cannot decide between the
> Predicate and the Function. Which means the Closure in the will have to be
> typed by you.
>
> And then of course we could get generics into the game.
>
> class X<T> {
>>   def foo(Function<T,Boolean> f) {..}
>>   def foo(Predicate<T> f) {..}
>> }
>>
>> def x = new X<MyType>()
>> x.foo {Integer x-> x+1>0}
>>
>
> Now for which values of MyType is this a legal call? at runtime we do not
> even have the information that x is a X<MyType>. Since we do not have that
> information, we cannot know what the T in the declaration of X means. So
> even though we used a type for x, we can still not decide here!
>
> And then there is the problem of the return type. What is the type of x+1
> in dynamic Groovy? We actually do not know. For x+1>0 we can infer that it
> must be boolean, because of using compareTo and that only returns a boolean
> and is enforced. But for arbitrary expressions we simply do not have the
> information before the result object is there and that is too late for the
> method selection of the method that will most likely do the invocation in
> the first place (or the invocation may never happen).
>
> So yes, we can improve here. But there are heavy limits for this static
> compilation controlled logic in dynamic Groovy.
>
> bye Jochen
>
>
>

Re: SAM type closure coercion

Posted by Jochen Theodorou <bl...@gmx.org>.

On 18.01.2017 12:33, Remi Forax wrote:
> I agree with Jesper,
> solving all the cases is an uncanny valley.
>
> The JEP 302 [1] has a good introduction on what javac does or not in
> case of overloading
> (and what currently does not work but is expected to work with java 10).
>
> Also the warning proposed by Jesper exists in javac under the name
> 'overloads'.
>
> cheers,
> R�mi
>
> [1] http://openjdk.java.net/jeps/302

What Groovy currently does is look if the parameter type is a SAM type 
and then mark this as a potential match with conversion. It does not 
take arity into account, neither generics, nor the returntype of the 
"lambda".

arity is a problem. {1} is a Groovy Closure with what arity? This can 
take one optional argument. {->1} is clear to take none and {x->1} is 
clear to take one argument. If we say you can use the shorter style, but 
then you may get into trouble, this is fine.

Then there is of course not only the arity, but also the types of the 
parameters. {x->1} is an object taking Closure in Groovy. This is 
different from lambdas where the type of x may be defined by the SAM 
type we match against. So for example if I have foo(x->1+x>10) and there 
is a Predicate<String> as well as a Function<Integer,Boolean>, then of 
course the Function is supposed to be taken, but javac can try to first 
match against Predicate and if that does not work try to match against 
Function. It can see that for Integer, there is a plus method through 
unboxing, thus Function can work and if there are no further candidates 
the method selection is unambiguous, thus can be completed and no 
compilation error will happen. In dynamic Groovy we only see x of type 
Object. If there is a plus method is decided at runtime. But as such we 
cannot decide between the Predicate and the Function. Which means the 
Closure in the will have to be typed by you.

And then of course we could get generics into the game.

> class X<T> {
>   def foo(Function<T,Boolean> f) {..}
>   def foo(Predicate<T> f) {..}
> }
>
> def x = new X<MyType>()
> x.foo {Integer x-> x+1>0}

Now for which values of MyType is this a legal call? at runtime we do 
not even have the information that x is a X<MyType>. Since we do not 
have that information, we cannot know what the T in the declaration of X 
means. So even though we used a type for x, we can still not decide here!

And then there is the problem of the return type. What is the type of 
x+1 in dynamic Groovy? We actually do not know. For x+1>0 we can infer 
that it must be boolean, because of using compareTo and that only 
returns a boolean and is enforced. But for arbitrary expressions we 
simply do not have the information before the result object is there and 
that is too late for the method selection of the method that will most 
likely do the invocation in the first place (or the invocation may never 
happen).

So yes, we can improve here. But there are heavy limits for this static 
compilation controlled logic in dynamic Groovy.

bye Jochen



Re: SAM type closure coercion

Posted by Remi Forax <fo...@univ-mlv.fr>.
I agree with Jesper, 
solving all the cases is an uncanny valley. 

The JEP 302 [1] has a good introduction on what javac does or not in case of overloading 
(and what currently does not work but is expected to work with java 10). 

Also the warning proposed by Jesper exists in javac under the name 'overloads'. 

cheers, 
Rémi 

[1] http://openjdk.java.net/jeps/302 

> De: "Jesper Steen Møller" <je...@selskabet.org>
> À: dev@groovy.apache.org
> Envoyé: Mercredi 18 Janvier 2017 12:20:05
> Objet: Re: SAM type closure coercion

> A word of warning: Solving this according to the spec is a hard problem
> (speaking from experience with the Eclipse compiler when supporting Java 8),
> especially if you add parameterized types to the equation. To quote the JLS:
> "In JSR 335, the greatest complexity lurked in the interaction of implicitly
> typed lambda expressions with overload resolution.”

> Checking the arity of the closure will likely fix many problems, but some of the
> really tricky ones (involving specificity rules) could take weeks.

> Couldn’t we add a warning if we detect an overloaded method being called with a
> closure as an argument?

> -Jesper

>> On 18 Jan 2017, at 12.04, Cédric Champeau < cedric.champeau@gmail.com > wrote:

>> This is something that we can improve. We knew it when we implemented SAM type
>> coercion, and decided to wait until someone complains, to see how often this
>> use case happens :)

>> 2017-01-18 11:59 GMT+01:00 Andres Almiray < aalmiray@gmail.com > :

>>> Hello everyone,

>>> Just yesterday Greg L. Turnquist blogged about an usage pattern of Spinnaker,
>>> Cloud Foundry, and Groovy. See
>>> http://greglturnquist.com/2017/01/reactively-talking-to-cloud-foundry-with-groovy.html

>>> Basically he complains that Groovy can't coerce a Closure to a given SAM type.
>>> In his own words

>>> "Groovy has this nice feature where it can coerce objects. However, with all the
>>> overloading, Groovy gets lost and can’t tell which TupleUtils function to
>>> target."

>>> Now I know that based on historical reasons Groovy did not coerce closures into
>>> SAMs until very "recently" (was it 2.2?). We also gained @DelegatesTo in order
>>> to supply additional hints to the compiler (@CompileStatic and @TypeChecked)
>>> and IDEs. Despite all this Groovy does not offer a "clean" solution to
>>> automatically coerce a closure into a SAM.

>>> Take for example the following Java code:

>>> ----
>>> public interface Function1 { void call(String arg0); }

>>> public interface Function2 { void call(String arg0, String arg1); }

>>> import groovy.lang.DelegatesTo;
>>> public class API {
>>> public void doit(@DelegatesTo Function1 func) {
>>> System.out.println("Invoking "+ func.toString());
>>> func.call("arg0");
>>> }

>>> public void doit(@DelegatesTo Function2 func) {
>>> System.out.println("Invoking "+ func.toString());
>>> func.call("arg0", "arg1");
>>> }
>>> }
>>> -----

>>> Invoking an instance of API from Groovy

>>> ----
>>> class Main {
>>> static void main(String[] args) {
>>> API api = new API()
>>> api.doit({ String arg0 -> println "Received $arg0" })
>>> api.doit({ String arg0, String arg1 -> println "Received $arg0 $arg1" })
>>> }
>>> }
>>> ----

>>> Results in a runtime exception such as

>>> Exception in thread "main" groovy.lang.GroovyRuntimeException: Ambiguous method
>>> overloading for method sample.API#doit.
>>> Cannot resolve which method to invoke for [class sample.Main$_main_closure1] due
>>> to overlapping prototypes between:
>>> [interface sample.Function1]
>>> [interface sample.Function2]
>>> at groovy.lang.MetaClassImpl.chooseMostSpecificParams(MetaClassImpl.java:3263)
>>> at groovy.lang.MetaClassImpl.chooseMethodInternal(MetaClassImpl.java:3216)
>>> at groovy.lang.MetaClassImpl.chooseMethod(MetaClassImpl.java:3159)
>>> at
>>> groovy.lang.MetaClassImpl.getMethodWithCachingInternal(MetaClassImpl.java:1336)
>>> at groovy.lang.MetaClassImpl.createPojoCallSite(MetaClassImpl.java:3391)
>>> at
>>> org.codehaus.groovy.runtime.callsite.CallSiteArray.createPojoSite(CallSiteArray.java:132)
>>> at
>>> org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallSite(CallSiteArray.java:166)
>>> at
>>> org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
>>> at
>>> org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
>>> at
>>> org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
>>> at sample.Main.main(Main.groovy:6)

>>> Activating static type checking yields

>>> /private/tmp/foo/src/main/groovy/sample/Main.groovy: 7: [Static type checking] -
>>> Reference to method is ambiguous. Cannot choose between [void
>>> sample.API#doit(sample.Function1), void sample.API#doit(sample.Function2)]
>>> @ line 7, column 9.
>>> api.doit({ String arg0 -> println "Received $arg0" })
>>> ^

>>> /private/tmp/foo/src/main/groovy/sample/Main.groovy: 8: [Static type checking] -
>>> Reference to method is ambiguous. Cannot choose between [void
>>> sample.API#doit(sample.Function1), void sample.API#doit(sample.Function2)]
>>> @ line 8, column 9.
>>> api.doit({ String arg0, String arg1 -> println "Received $arg0 $arg1" })

>>> Clearly Groovy requires some hints to determine the first closure must be
>>> coerced to Function1 and the second to Function2. Not even @DelegatesTo helps
>>> in this case.

>>> The current "workaround" (from a Java dev POV anyway) is to sprinkle the code
>>> with usages of the 'as' keyword to explicitly coerce a closure into a target
>>> type. And this is exactly where the Java interop history breaks due to Java 8,
>>> because as we know Java 8 lambda expressions are automatically coerced into a
>>> matching SAM type.

>>> Does the parrot parser offer an alternative to this problem?

>>> Can the compiler be aware of additional arg/type information provided by the
>>> Closure to figure out the right SAM type to use?

>>> Cheers,
>>> Andres

>>> -------------------------------------------
>>> Java Champion; Groovy Enthusiast
>>> http://jroller.com/aalmiray
>>> http://www.linkedin.com/in/aalmiray
>>> --
>>> What goes up, must come down. Ask any system administrator.
>>> There are 10 types of people in the world: Those who understand binary, and
>>> those who don't.
>>> To understand recursion, we must first understand recursion.

Re: SAM type closure coercion

Posted by Jesper Steen Møller <je...@selskabet.org>.
A word of warning: Solving this according to the spec is a hard problem (speaking from experience with the Eclipse compiler when supporting Java 8), especially if you add parameterized types to the equation. To quote the JLS: "In JSR 335, the greatest complexity lurked in the interaction of implicitly typed lambda expressions with overload resolution.”

Checking the arity of the closure will likely fix many problems, but some of the really tricky ones (involving specificity rules) could take weeks.

Couldn’t we add a warning if we detect an overloaded method being called with a closure as an argument?

-Jesper

> On 18 Jan 2017, at 12.04, Cédric Champeau <ce...@gmail.com> wrote:
> 
> This is something that we can improve. We knew it when we implemented SAM type coercion, and decided to wait until someone complains, to see how often this use case happens :)
> 
> 2017-01-18 11:59 GMT+01:00 Andres Almiray <aalmiray@gmail.com <ma...@gmail.com>>:
> Hello everyone,
> 
> Just yesterday Greg L. Turnquist blogged about an usage pattern of Spinnaker, Cloud Foundry, and Groovy. See http://greglturnquist.com/2017/01/reactively-talking-to-cloud-foundry-with-groovy.html <http://greglturnquist.com/2017/01/reactively-talking-to-cloud-foundry-with-groovy.html>
> 
> Basically he complains that Groovy can't coerce a Closure to a given SAM type. In his own words
> 
> "Groovy has this nice feature where it can coerce objects. However, with all the overloading, Groovy gets lost and can’t tell which TupleUtils function to target."
> 
> Now I know that based on historical reasons Groovy did not coerce closures into SAMs until very "recently" (was it 2.2?). We also gained @DelegatesTo in order to supply additional hints to the compiler (@CompileStatic and @TypeChecked) and IDEs. Despite all this Groovy does not offer a "clean" solution to automatically coerce a closure into a SAM.
> 
> Take for example the following Java code:
> 
> ----
> public interface Function1 { void call(String arg0); }
> 
> public interface Function2 { void call(String arg0, String arg1); }
> 
> import groovy.lang.DelegatesTo;
> public class API {
>     public void doit(@DelegatesTo Function1 func) {
>         System.out.println("Invoking "+ func.toString());
>         func.call("arg0");
>     }
> 
>     public void doit(@DelegatesTo Function2 func) {
>         System.out.println("Invoking "+ func.toString());
>         func.call("arg0", "arg1");
>     }
> }
> -----
> 
> Invoking an instance of API from Groovy
> 
> ----
> class Main {
>     static void main(String[] args) {
>         API api = new API()
>         api.doit({ String arg0 -> println "Received $arg0" })
>         api.doit({ String arg0, String arg1 -> println "Received $arg0 $arg1" })
>     }
> }
> ----
> 
> Results in a runtime exception such as 
> 
> Exception in thread "main" groovy.lang.GroovyRuntimeException: Ambiguous method overloading for method sample.API#doit.
> Cannot resolve which method to invoke for [class sample.Main$_main_closure1] due to overlapping prototypes between:
>         [interface sample.Function1]
>         [interface sample.Function2]
>         at groovy.lang.MetaClassImpl.chooseMostSpecificParams(MetaClassImpl.java:3263)
>         at groovy.lang.MetaClassImpl.chooseMethodInternal(MetaClassImpl.java:3216)
>         at groovy.lang.MetaClassImpl.chooseMethod(MetaClassImpl.java:3159)
>         at groovy.lang.MetaClassImpl.getMethodWithCachingInternal(MetaClassImpl.java:1336)
>         at groovy.lang.MetaClassImpl.createPojoCallSite(MetaClassImpl.java:3391)
>         at org.codehaus.groovy.runtime.callsite.CallSiteArray.createPojoSite(CallSiteArray.java:132)
>         at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallSite(CallSiteArray.java:166)
>         at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
>         at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
>         at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
>         at sample.Main.main(Main.groovy:6)
> 
> Activating static type checking yields
> 
> /private/tmp/foo/src/main/groovy/sample/Main.groovy: 7: [Static type checking] - Reference to method is ambiguous. Cannot choose between [void sample.API#doit(sample.Function1), void sample.API#doit(sample.Function2)]
>  @ line 7, column 9.
>            api.doit({ String arg0 -> println "Received $arg0" })
>            ^
> 
> /private/tmp/foo/src/main/groovy/sample/Main.groovy: 8: [Static type checking] - Reference to method is ambiguous. Cannot choose between [void sample.API#doit(sample.Function1), void sample.API#doit(sample.Function2)]
>  @ line 8, column 9.
>            api.doit({ String arg0, String arg1 -> println "Received $arg0 $arg1" })
> 
> Clearly Groovy requires some hints to determine the first closure must be coerced to Function1 and the second to Function2. Not even @DelegatesTo helps in this case.
> 
> The current "workaround" (from a Java dev POV anyway) is to sprinkle the code with usages of the 'as' keyword to explicitly coerce a closure into a target type. And this is exactly where the Java interop history breaks due to Java 8, because as we know Java 8 lambda expressions are automatically coerced into a matching SAM type.
> 
> Does the parrot parser offer an alternative to this problem?
> 
> Can the compiler be aware of additional arg/type information provided by the Closure to figure out the right SAM type to use?
> 
> Cheers,
> Andres
> 
> -------------------------------------------
> Java Champion; Groovy Enthusiast
> http://jroller.com/aalmiray <http://jroller.com/aalmiray>
> http://www.linkedin.com/in/aalmiray <http://www.linkedin.com/in/aalmiray>
> --
> What goes up, must come down. Ask any system administrator.
> There are 10 types of people in the world: Those who understand binary, and those who don't.
> To understand recursion, we must first understand recursion.
> 


Re: SAM type closure coercion

Posted by Marcin Erdmann <ma...@proxerd.pl>.
Was that the one case when moaning about other people's work is not frowned
upon? Damn, I wish I knew, I would unleash my moaning in public!

But seriously, this limitation is very annoying when you work with Ratpack
in Groovy.

I have moved towards more type safe coding recently and when I learned
about SAM coercion I started using SAM functional interfaces in signatures
of methods and then passing closures to these methods. That way it's easier
to figure out what a method parameter takes and returnes without having to
look at method implementation. So any improvements in that area are most
welcome.

On Wed, 18 Jan 2017 at 11:04, Cédric Champeau <ce...@gmail.com>
wrote:

> This is something that we can improve. We knew it when we implemented SAM
> type coercion, and decided to wait until someone complains, to see how
> often this use case happens :)
>
> 2017-01-18 11:59 GMT+01:00 Andres Almiray <aa...@gmail.com>:
>
> Hello everyone,
>
> Just yesterday Greg L. Turnquist blogged about an usage pattern of
> Spinnaker, Cloud Foundry, and Groovy. See
> http://greglturnquist.com/2017/01/reactively-talking-to-cloud-foundry-with-groovy.html
>
> Basically he complains that Groovy can't coerce a Closure to a given SAM
> type. In his own words
>
> "Groovy has this nice feature where it can coerce objects. However, with
> all the overloading, Groovy gets lost and can’t tell which TupleUtils
> function to target."
>
> Now I know that based on historical reasons Groovy did not coerce closures
> into SAMs until very "recently" (was it 2.2?). We also gained @DelegatesTo
> in order to supply additional hints to the compiler (@CompileStatic and
> @TypeChecked) and IDEs. Despite all this Groovy does not offer a "clean"
> solution to automatically coerce a closure into a SAM.
>
> Take for example the following Java code:
>
> ----
> public interface Function1 { void call(String arg0); }
>
> public interface Function2 { void call(String arg0, String arg1); }
>
> import groovy.lang.DelegatesTo;
> public class API {
>     public void doit(@DelegatesTo Function1 func) {
>         System.out.println("Invoking "+ func.toString());
>         func.call("arg0");
>     }
>
>     public void doit(@DelegatesTo Function2 func) {
>         System.out.println("Invoking "+ func.toString());
>         func.call("arg0", "arg1");
>     }
> }
> -----
>
> Invoking an instance of API from Groovy
>
> ----
> class Main {
>     static void main(String[] args) {
>         API api = new API()
>         api.doit({ String arg0 -> println "Received $arg0" })
>         api.doit({ String arg0, String arg1 -> println "Received $arg0
> $arg1" })
>     }
> }
> ----
>
> Results in a runtime exception such as
>
> Exception in thread "main" groovy.lang.GroovyRuntimeException: Ambiguous
> method overloading for method sample.API#doit.
> Cannot resolve which method to invoke for [class
> sample.Main$_main_closure1] due to overlapping prototypes between:
>         [interface sample.Function1]
>         [interface sample.Function2]
>         at
> groovy.lang.MetaClassImpl.chooseMostSpecificParams(MetaClassImpl.java:3263)
>         at
> groovy.lang.MetaClassImpl.chooseMethodInternal(MetaClassImpl.java:3216)
>         at groovy.lang.MetaClassImpl.chooseMethod(MetaClassImpl.java:3159)
>         at
> groovy.lang.MetaClassImpl.getMethodWithCachingInternal(MetaClassImpl.java:1336)
>         at
> groovy.lang.MetaClassImpl.createPojoCallSite(MetaClassImpl.java:3391)
>         at
> org.codehaus.groovy.runtime.callsite.CallSiteArray.createPojoSite(CallSiteArray.java:132)
>         at
> org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallSite(CallSiteArray.java:166)
>         at
> org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
>         at
> org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
>         at
> org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
>         at sample.Main.main(Main.groovy:6)
>
> Activating static type checking yields
>
> /private/tmp/foo/src/main/groovy/sample/Main.groovy: 7: [Static type
> checking] - Reference to method is ambiguous. Cannot choose between [void
> sample.API#doit(sample.Function1), void sample.API#doit(sample.Function2)]
>  @ line 7, column 9.
>            api.doit({ String arg0 -> println "Received $arg0" })
>            ^
>
> /private/tmp/foo/src/main/groovy/sample/Main.groovy: 8: [Static type
> checking] - Reference to method is ambiguous. Cannot choose between [void
> sample.API#doit(sample.Function1), void sample.API#doit(sample.Function2)]
>  @ line 8, column 9.
>            api.doit({ String arg0, String arg1 -> println "Received $arg0
> $arg1" })
>
> Clearly Groovy requires some hints to determine the first closure must be
> coerced to Function1 and the second to Function2. Not even @DelegatesTo
> helps in this case.
>
> The current "workaround" (from a Java dev POV anyway) is to sprinkle the
> code with usages of the 'as' keyword to explicitly coerce a closure into a
> target type. And this is exactly where the Java interop history breaks due
> to Java 8, because as we know Java 8 lambda expressions are automatically
> coerced into a matching SAM type.
>
> Does the parrot parser offer an alternative to this problem?
>
> Can the compiler be aware of additional arg/type information provided by
> the Closure to figure out the right SAM type to use?
>
> Cheers,
> Andres
>
> -------------------------------------------
> Java Champion; Groovy Enthusiast
> http://jroller.com/aalmiray
> http://www.linkedin.com/in/aalmiray
> --
> What goes up, must come down. Ask any system administrator.
> There are 10 types of people in the world: Those who understand binary,
> and those who don't.
> To understand recursion, we must first understand recursion.
>
>
>
>
>
>
>
>

Re: SAM type closure coercion

Posted by Cédric Champeau <ce...@gmail.com>.
This is something that we can improve. We knew it when we implemented SAM
type coercion, and decided to wait until someone complains, to see how
often this use case happens :)

2017-01-18 11:59 GMT+01:00 Andres Almiray <aa...@gmail.com>:

> Hello everyone,
>
> Just yesterday Greg L. Turnquist blogged about an usage pattern of
> Spinnaker, Cloud Foundry, and Groovy. See http://greglturnquist.com/
> 2017/01/reactively-talking-to-cloud-foundry-with-groovy.html
>
> Basically he complains that Groovy can't coerce a Closure to a given SAM
> type. In his own words
>
> "Groovy has this nice feature where it can coerce objects. However, with
> all the overloading, Groovy gets lost and can’t tell which TupleUtils
> function to target."
>
> Now I know that based on historical reasons Groovy did not coerce closures
> into SAMs until very "recently" (was it 2.2?). We also gained @DelegatesTo
> in order to supply additional hints to the compiler (@CompileStatic and
> @TypeChecked) and IDEs. Despite all this Groovy does not offer a "clean"
> solution to automatically coerce a closure into a SAM.
>
> Take for example the following Java code:
>
> ----
> public interface Function1 { void call(String arg0); }
>
> public interface Function2 { void call(String arg0, String arg1); }
>
> import groovy.lang.DelegatesTo;
> public class API {
>     public void doit(@DelegatesTo Function1 func) {
>         System.out.println("Invoking "+ func.toString());
>         func.call("arg0");
>     }
>
>     public void doit(@DelegatesTo Function2 func) {
>         System.out.println("Invoking "+ func.toString());
>         func.call("arg0", "arg1");
>     }
> }
> -----
>
> Invoking an instance of API from Groovy
>
> ----
> class Main {
>     static void main(String[] args) {
>         API api = new API()
>         api.doit({ String arg0 -> println "Received $arg0" })
>         api.doit({ String arg0, String arg1 -> println "Received $arg0
> $arg1" })
>     }
> }
> ----
>
> Results in a runtime exception such as
>
> Exception in thread "main" groovy.lang.GroovyRuntimeException: Ambiguous
> method overloading for method sample.API#doit.
> Cannot resolve which method to invoke for [class
> sample.Main$_main_closure1] due to overlapping prototypes between:
>         [interface sample.Function1]
>         [interface sample.Function2]
>         at groovy.lang.MetaClassImpl.chooseMostSpecificParams(
> MetaClassImpl.java:3263)
>         at groovy.lang.MetaClassImpl.chooseMethodInternal(
> MetaClassImpl.java:3216)
>         at groovy.lang.MetaClassImpl.chooseMethod(MetaClassImpl.java:3159)
>         at groovy.lang.MetaClassImpl.getMethodWithCachingInternal(
> MetaClassImpl.java:1336)
>         at groovy.lang.MetaClassImpl.createPojoCallSite(
> MetaClassImpl.java:3391)
>         at org.codehaus.groovy.runtime.callsite.CallSiteArray.
> createPojoSite(CallSiteArray.java:132)
>         at org.codehaus.groovy.runtime.callsite.CallSiteArray.
> createCallSite(CallSiteArray.java:166)
>         at org.codehaus.groovy.runtime.callsite.CallSiteArray.
> defaultCall(CallSiteArray.java:48)
>         at org.codehaus.groovy.runtime.callsite.AbstractCallSite.
> call(AbstractCallSite.java:113)
>         at org.codehaus.groovy.runtime.callsite.AbstractCallSite.
> call(AbstractCallSite.java:125)
>         at sample.Main.main(Main.groovy:6)
>
> Activating static type checking yields
>
> /private/tmp/foo/src/main/groovy/sample/Main.groovy: 7: [Static type
> checking] - Reference to method is ambiguous. Cannot choose between [void
> sample.API#doit(sample.Function1), void sample.API#doit(sample.Function2)]
>  @ line 7, column 9.
>            api.doit({ String arg0 -> println "Received $arg0" })
>            ^
>
> /private/tmp/foo/src/main/groovy/sample/Main.groovy: 8: [Static type
> checking] - Reference to method is ambiguous. Cannot choose between [void
> sample.API#doit(sample.Function1), void sample.API#doit(sample.Function2)]
>  @ line 8, column 9.
>            api.doit({ String arg0, String arg1 -> println "Received $arg0
> $arg1" })
>
> Clearly Groovy requires some hints to determine the first closure must be
> coerced to Function1 and the second to Function2. Not even @DelegatesTo
> helps in this case.
>
> The current "workaround" (from a Java dev POV anyway) is to sprinkle the
> code with usages of the 'as' keyword to explicitly coerce a closure into a
> target type. And this is exactly where the Java interop history breaks due
> to Java 8, because as we know Java 8 lambda expressions are automatically
> coerced into a matching SAM type.
>
> Does the parrot parser offer an alternative to this problem?
>
> Can the compiler be aware of additional arg/type information provided by
> the Closure to figure out the right SAM type to use?
>
> Cheers,
> Andres
>
> -------------------------------------------
> Java Champion; Groovy Enthusiast
> http://jroller.com/aalmiray
> http://www.linkedin.com/in/aalmiray
> --
> What goes up, must come down. Ask any system administrator.
> There are 10 types of people in the world: Those who understand binary,
> and those who don't.
> To understand recursion, we must first understand recursion.
>