Methods and Closures
Also see [groovy_site, 3.3. Methods].
def sum(int a, int b=0) { // parameter `b` has the default value
a + b // no need for the `return` operator (though it may be used if we would like)
}
println sum(7, 4) // 11
println sum(7) // 7
In Groovy, the last expression evaluated in the body of a method can be returned without necessitating the
return
keyword. Especially for short methods and for closures, it's nicer to omit it for brevity.
Also see Intermediate Java code introspection and Running by Java runtime.
>groovyc methods.groovy
>dir /b | find "methods.class"
methods.class
>java -cp ".;%GROOVY_HOME%\lib\*" methods
11
7
>java -jar %CFR_HOME%cfr-0.152.jar methods.class > methods.java
>del methods.class
Here we also ran the generated class with Java runtime to confirm that it works.
The result is:
public class methods
extends Script {
...
public Object run() {
IndyInterface.bootstrap("invoke", "println", 2, this, IndyInterface.bootstrap("invoke", "sum", 2, this, 7, 4));
return IndyInterface.bootstrap("invoke", "println", 2, this, IndyInterface.bootstrap("invoke", "sum", 2, this, 7));
}
public Object sum(int a, int b) {
return IndyInterface.bootstrap("invoke", "plus", 0, a, b);
}
@Generated
public Object sum(int a) {
return this.sum(a, 0);
}
...
}
So we see a separate overloaded method was added to provide the default method parameter value feature.
Also see [groovy_site, Closures].
def name = 'Bob'
def sayHello() {
println "Hello, $name!" // CANNOT use external variables
}
// sayHello() // groovy.lang.MissingPropertyException: No such property: name for class: methods
def myClosure = {-> println "Hello, $name!"} // CAN use external variables
// `->` restricts the closure to be called without arguments (otherwise it will be called with
// the implicit argument `it`)
myClosure.call() // Hello, Bob!
myClosure() // Hello, Bob!
def myList = [1, 2, 3, 4, 5, 6, 7, 8]
println myList.find {it > 3} // 4 // `it` is the implicit argument
// Delegation
class Alice {
public name = "Alice"
}
class Bob {
public name = "Bob"
}
def closure = {println delegate.name} // by now the `name` variable from the module is assumed
println closure.delegate.class.getName() // closures
closure.delegate = Alice
closure() // Alice
closure.delegate = Bob
closure() // Bob
This may look strange that the global variable name
is not visible inside the method sayHello()
,
this is investigated here.
Interesting thing here is the fact that the Alice
and the Bob
classes are different and their
name
attributes are resolved dynamically at run time. Also see [groovy_site,
3. Delegation strategy].
def myName = 'John'
def sayHello() {
//println "Hello from method, $myName!" // CANNOT use external variables
}
def myClosure = {-> println "Hello from closure, $myName!"} // CAN use external variables
sayHello() // prints nothing
myClosure() // prints "Hello from closure, John!"
We are curious about why the global variable myName
is not visible inside the method sayHello()
,
so want to see what happens under the hood (also see
Intermediate Java code introspection):
>groovyc -d=closures_scopes_classes closures_scopes.groovy
>cd closures_scopes_classes
>dir /b
closures_scopes$_run_closure1.class
closures_scopes.class
>java -cp ".;%GROOVY_HOME%\lib\*" closures_scopes
Hello from closure, John!
>java -jar %CFR_HOME%cfr-0.152.jar closures_scopes.class > closures_scopes.java
>java -jar %CFR_HOME%cfr-0.152.jar closures_scopes$_run_closure1.class > closures_scopes$_run_closure1.java
>del closures_scopes$_run_closure1.class
>del closures_scopes.class
As expected a separate class was generated for the closure.
...
public final class closures_scopes._run_closure1
extends Closure
implements GeneratedClosure {
private /* synthetic */ Reference myName;
...
public closures_scopes._run_closure1(Object _outerInstance, Object _thisObject, Reference myName) {
super(_outerInstance, _thisObject);
Reference reference;
this.myName = reference = myName;
}
public Object doCall() {
return IndyInterface.bootstrap("invoke", "println", 2, this, new GStringImpl(
new Object[]{this.myName.get()}, new String[]{"Hello from closure, ", "!"}));
}
@Generated
public Object getMyName() {
return this.myName.get();
}
...
}
...
public class closures_scopes
extends Script {
...
public Object run() {
Reference myName = new Reference((Object)"John");
public final class _run_closure1
extends Closure
implements GeneratedClosure {
private /* synthetic */ Reference myName;
...
public _run_closure1(Object _outerInstance, Object _thisObject, Reference myName) {
super(_outerInstance, _thisObject);
Reference reference;
this.myName = reference = myName;
}
public Object doCall() {
return IndyInterface.bootstrap("invoke", "println", 2, this, new GStringImpl(
new Object[]{this.myName.get()}, new String[]{"Hello from closure, ", "!"}));
}
@Generated
public Object getMyName() {
return this.myName.get();
}
...
}
_run_closure1 myClosure = new _run_closure1((Object)this, (Object)this, myName);
IndyInterface.bootstrap("invoke", "sayHello", 2, this);
return IndyInterface.bootstrap("invoke", "call", 0, myClosure);
}
public Object sayHello() {
return null;
}
...
}
As we can see the so-called "global variable" myName
is actually defined as a local variable
inside the run()
method. The sayHello()
method is defined outside the run()
method and called
inside the run()
method. So it cannot see the myName
variable.
As for the closure, its instance is created inside the run()
method and is given a reference to
the myName
variable. So when it's invoked it can access the myName
variable.
Note
As we can see the closure _run_closure1
is defined twice: in its own class file
closures_scopes$_run_closure1.class
and inside the "main" class file closures_scopes.class
(recall that they were decompiled). There's code duplication there.
This is very likely caused by the inner class inlining by ether groovyc
or decompiler.
In actual bytecode there may probably be a top-level class for the closure and a reference
to it in the script class. But even if actually inlining was done then the meaning of this
investigation are still the same, they are just less clear.
The full decompiled Java files are: closures_scopes.java and closures_scopes$_run_closure1.java.
@Field
annotationAs we saw above though closures let us use external variables their implementation is much more complex and probably it's an overkill if only global script variables access is required. Following is an alternative solution:
import groovy.transform.Field
@Field
def myName = 'John'
def sayHello() {
println "Hello from method, $myName!" // now CAN use external variables
}
def myClosure = {-> println "Hello from closure, $myName!"} // still CAN use external variables
sayHello() // prints "Hello from method, John!"
myClosure() // prints "Hello from closure, John!"
The @Field
annotation tells the Groovy compiler to turn this variable into a field of the
generated script class — not a local variable inside the run()
method — which makes it
accessible from both methods and closures. The class file investigation is probably not required.
Following are some considerations.
run()
method in a script), so they "just work" without extra
annotations@Field
+ method. Methods are better when:@Memoized
, which closures can't do.@Field
and methods
gives us something closer to class-like structure — helpful if our script is getting
large@Field
when you're thinking "structured logic using shared state"Also see [groovy_site, 6.1. Currying] and [groovy_wiki, Curry].
Usually called partial application, this Groovy feature allows closures' parameters to be set to a default parameter in any of their arguments, creating a new closure with the bound value. Supplying one argument to the
curry()
method will fix argument one. SupplyingN
arguments will fix arguments1..N
.
def nCopies = { int n, String str -> str * n }
def twice = nCopies.curry(2) // left currying
println twice("go ") // go go
def hi = nCopies.rcurry('hi ') // right currying
println hi(2) // hi hi
def volume = { double l, double w, double h -> l * w * h }
def area = volume.ncurry(1, 1d)
println volume(2, 1, 3) // 6.0
println area(2, 3) // 6.0