Sidebar LogoHomeIndexIndexGitHub </> Back Next Methods and Closures

Methods and Closures


Methods

Also see [groovy_site, 3.3. Methods].

project1/methods.groovy
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.

— [groovy_site, 2. Return keyword optional]

Introspection

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:

project1/methods.java
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.


Closures

Also see [groovy_site, Closures].

project1/closures.groovy
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].


Method and closure visibility scope

project1/closures_scopes.groovy
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.

project1/closures_scopes_classes/closures_scopes$_run_closure1.java
...
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();
    }
    ...
}
project1/closures_scopes_classes/closures_scopes.java
...
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 annotation

As 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.


Curries

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. Supplying N arguments will fix arguments 1..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

 


Back Next