Skip to content

Closures

Philip Ford edited this page Aug 27, 2017 · 30 revisions

Contents

Summary

Groovy closures are true closures, with access to the variables in the context in which they are defined. They are:

  • Contained in braces

  • They are functions.

  • But they are also instances of the Closure class.

  • Used in callbacks

  • The default parameter (if you do not provide parameters) is it.

    [1,2,3].each {
       println it
    }

Closures vs. Lambdas

Syntactically, closures look a lot like lambdas, and they can be passed as parameters to another function. In other words, they are first-class objects. However, closures are instances of the Closure class, making them very different from Java 8 lambdas.

In addition, delegation is a key concept in Groovy closures which has no equivalent in lambdas. The ability to change the delegate or change the delegation strategy of closures make it possible to design beautiful domain specific languages (DSLs) in Groovy.

Syntax

{ <type> param1, <type> param2 ... ->
   //Body
}
  • Again, parameters are optional.
  • If no parameters are listed, the default parameter is it.
  • Parameters, if present, are listed (comma-separated) immediately after the first brace, on the first line of the closure, and followed by a arrow.
  • The code body begins on the next line.
  • The output of the last line is the return value, with or without the return statement.

Invoking Closures

Closures can be invoked like any other function:

closureName(<params...>)

Alternatively, closures can be invoked with the call method of the Closure class:

def c = { 123 }          // returns 123
assert c() == 123        // true
assert c.call()  == 123  // true

Parameters

Parameters of closures follow the same principle as parameters of regular methods:

  • an optional type
  • a name
  • an optional default value

In addition, parameters are separated by commas.

Implicit Parameter

When a closure does not explicitly define a parameter list (using ->), a closure always defines an implicit parameter, named it.

def greeting = { "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'

// The closure above is strictly equivalent to this one:
def greeting = { it -> "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'

If you want to declare a closure which accepts no argument and must be restricted to calls without arguments, then you must declare it with an explicit empty argument list:

// Declaring a closure with no parameters allowed.
def magicNumber = { -> 42 }

// this call will fail because the closure doesn't accept any argument
magicNumber(11)

Varargs

It is possible for a closure to declare variable arguments like any other method. Varargs methods are methods that can accept a variable number of arguments if the last parameter is of variable length (or is an array):

def concat1 = { String... args -> args.join('') }           
assert concat1('abc','def') == 'abcdef'    

// The same behavior is directly available if the args parameter is declared as an array                 
def concat2 = { String[] args -> args.join('') }            
assert concat2('abc', 'def') == 'abcdef'

// Again, varargs work as long as the last parameter is an array or an explicit varags type
def multiConcat = { int n, String... args ->                
    args.join('')*n
}
assert multiConcat(2, 'abc','def') == 'abcdefabcdef'

Closures as Parameters

For a function/closure to take a Closure as a parameter, that parameter must be declared as the Closure datatype.1

Example

private defaultResponseHandler = { resp, reader ->
   assert resp.status == 200
   println "My response handler got response: ${resp.statusLine}"
   println "Response length: ${resp.headers.'Content-Length'}"
   System.out << reader // print response reader
}

void fetchContent(String path, Closure c = defaultResponseHandler){
    connect path, TEXT, c
}

In this case, from my HttpClient, I make the Closure parameter optional by giving it a default value. In either case, the closure works as a callback.

Delegation

Owner, delegate, and this

To understand the concept of delegates, we must first understand the meaning of this inside a closure. A closure actually defines 3 distinct things:

  • this, which corresponds to the current instance of the enclosing class where the closure is defined
  • owner, which corresponds to the enclosing object where the closure is defined, which may be either an instance of class or a closure
  • delegate, which corresponds to a third party object where methods calls or properties are resolved whenever the receiver of the message is not defined

The meaning of this

In a closure, calling getThisObject will also return the current instance of the enclosing class where the closure is defined. It is equivalent to using an explicit this:

class Enclosing {
    void run() {
        def whatIsThisObject = { getThisObject() }    
        // calling the closure will return the instance of Enclosing where the the closure is defined
        assert whatIsThisObject() == this           // true      
        // Equivalent to whatIsThisObject above    
        def whatIsThis = { this }                           
        assert whatIsThis() == this                 // true                         
    }
}

// If the closure is defined in an inner class, 
// "this" returns the inner class, not the top-level one.
class EnclosedInInnerClass {
    class Inner {
        Closure cl = { this }                               
    }
    void run() {
        def inner = new Inner()
        assert inner.cl() == inner                          
    }
}

// Nested closures
class NestedClosures {
    void run() {
        def nestedClosures = {
            // Here, in the closure named `cl`, `this` corresponds to the closest outer class, 
            // not the enclosing closure!
            def cl = { this }                               
            cl()
        }
        assert nestedClosures() == this    // true:  "this" is the instance of the NestedClosures class, not the nestedClosures method.                  
    }
}
  • In general, you will want to use the shortcut this notation.
  • If the closure is defined in a inner class, this in the closure will return the inner class, not the top-level one.
  • The code inside the closure can call methods of the enclosing class through this.
    class Person {
        String name
        int age
        String toString() { "$name is $age years old" }
    
        String dump() {
            def cl = {
                String msg = this.toString()               
                println msg
                msg
            }
            cl()
        }
    }
    def p = new Person(name:'Janice', age:74)
    assert p.dump() == 'Janice is 74 years old'

The Owner of a Closure

The owner of a closure is very similar to the definition of this in a closure with a subtle difference: owner will return the direct enclosing object, be it a closure or a class:

class Enclosing {
    void run() {
        def whatIsOwnerMethod = { getOwner() }               
        assert whatIsOwnerMethod() == this      // true               
        def whatIsOwner = { owner }                          
        assert whatIsOwner() == this            // true                   
    }
}

// Again, as with "this", if the closure is defined in a inner class,
// "owner" in the closure will return the inner class, not the top-level one
class EnclosedInInnerClass {
    class Inner {
        Closure cl = { owner }                               
    }
    void run() {
        def inner = new Inner()
        assert inner.cl() == inner                           
    }
}
class NestedClosures {
    void run() {
        def nestedClosures = {
            def cl = { owner }                               
            cl()
        }
        assert nestedClosures() == nestedClosures            
    }
}
  • In general, you will want to use the shortcut owner notation, instead of getOwner().
  • Again, as with this, if the closure is defined in a inner class, owner in the closure will return the inner class, not the top-level one.

Functional Programming

Currying

Memoization

References

Notes

  1. Actually "must be declared" may be too strong. Perhaps def would work.

Clone this wiki locally