Search code examples
genericsgroovylambdaclosuresmethod-reference

Groovy3: how to get "Java lambdas" instead of closures?


I did some tests in Groovy3 and Java lambda expressions are mostly interpreted as closures instead of as Functional Interface instances. How can I make lambdas work with Generics in a practical way (without coercion) and how does Groovy4 compare?

@CompileStatic

// lambda (ok!)
Supplier<String> s = 5::toString
// decompiled
Supplier s = DefaultGroovyMethods::toString;

// closure (wth?)
Supplier<String> s
s = 5::toString
// decompiled
Supplier s = null;
Closure var3 = ScriptBytecodeAdapter.getMethodPointer(5, "toString"); 
s = (Supplier)ScriptBytecodeAdapter.castToType(var3, Supplier.class);

// lambda
void test(Supplier<String> s) {}
test(5::toString)
// decompiled
test(DefaultGroovyMethods::toString);

// fails with "Groovyc: The argument is a method reference, 
// but the parameter type is not a functional interface"
<T> void test(T s) {}
this.<Supplier<String>>test(5::toString)

// closure (fails without coercion)
List<Supplier<String>> list = [5::toString]

// lambda (works, but cumbersome)
Supplier<String> s = 5::toString
List<Supplier<String>> list = [s]

// closure (fails without coercion)
List<Supplier<String>> list = []
list.add(5::toString)

I would expect that statically compiled Java lambda syntax always enforces Java lambda semantics.


Solution

  • Lambdas and method references are a new feature for Groovy 3. With that in mind, the goal under Static Compilation (@CompileStatic) was to work like Java. However, there are some edge cases that have come in as bugs and have been fixed since Groovy 3.0.0 and some that have not. You have hit on some of these edge cases.

    When Groovy cannot detect that the target of a lambda or method reference expression is a functional interface, it falls back on closure or method pointer handling respectively. This is why you are seeing "ScriptBytecodeAdapter.getMethodPointer" for the split declaration.

    I have a bug fix that will go into Groovy 3.0.22 for the split declaration (see GROOVY-11363). This case should already be handled properly (as you would expect) in Groovy 4.

    Please try each of the cases above with the latest Groovy 5 alpha release (our master branch at this time) and if you find anything that does not work as you would expect, please open a new ticket here: https://issues.apache.org/jira/projects/GROOVY