groovy-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Marcin Erdmann <marcin.erdm...@proxerd.pl>
Subject Re: SAM type closure coercion
Date Thu, 19 Jan 2017 08:04:19 GMT
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 <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.
>
>
>
>
>
>
>
>

Mime
View raw message