groovy-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Andres Almiray <aalmi...@gmail.com>
Subject SAM type closure coercion
Date Wed, 18 Jan 2017 10:59:33 GMT
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