Closures

A closure is an open, anonymous, block of code that can take zero or arguments, always return a value and be assigned to a variable. A closure may reference variables declared in its surrounding scope they can also contain free variables which are defined outside of its surrounding scope. Closures are of type Closure and we can assign a closure to a variable or pass it as a parameter to a method, also closures should be small pieces of code and should not replace a complex function. Below are some examples on what you can use closures for

Closure basics
// create a closure use curly braces
def c = { }

println c.class.name
println c instanceof Closure                         // class of Closure

// closure is really an anonymous function
def sayHello = { name ->                             // name is a argument
    println "Hello $name"
}
sayHello("Paul")

List nums = [1,2,3,4,5]
nums.each({
   print "$it "                                      // 'it' keyword is the default name for each element
})
println ""

nums.each{ n ->
    print "$n "                                      // or you can name the element, n in this case
}
println ""

def timesTen(num, closure) {
    closure(num * 10)
}

timesTen(10, { println "$it "})
timesTen(2) {                                        // you can write above like this as well, if closure is last argument
    println "$it "
}

10.times {
    print "$it "
}
println ""

// Random is already imported
Random random = new Random()

3.times {
    println random.nextInt()
}
Closure parameters
// implicit parameter (it)
def foo = {
    println it                                      // keyword 'it' is used if no paraeter name is used
}
foo("Hello")

// make sure no parameters are passed
def noparams = { ->
    println "no params"
}
noparams()                                          // will throw a MissingMethod exception if parameter is passed

// multiple parameters
def sayHello = { first, last ->
    println "$first $last"
}
sayHello("Paul", "Valle")

// default values
def greet = { String name, String greeting = "Howdy" ->
    println "$greeting, $name"
}
greet("Paul", "Hello")
greet("Lorraine")

// var-arg
def concat = { String... args ->                     // same as Java vargs
    args.join(' ')
}
println concat("Hello", "World", "!!!")
println concat("What", "a", "nice", "day", "it is", "today")

// Passing closures to methods
def someMethod(Closure c){
    println "..."
    println c.maximumNumberOfParameters
    println c.parameterTypes
}

def someClosure = { int x, int y -> x + y}
someMethod(someClosure)

Collection Methods

I covered collections in my last section, a number of collection have methods that take a closure as a argument, here are some examples

Collection methods
// each & eachWithIndex
List favNums = [1,2,3,4,5,6,7,8,9]

for (num in favNums){
    print "$num "
}
println()

favNums.each {print "$it "}                                             // each can take a closure
println()
favNums.eachWithIndex{ int entry, int i -> print "$i:$entry "}          // eachWithIndex can also take a closure
println()

// findAll
List days = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]
List weekend = days.findAll {it.startsWith("S")}
print weekend
println()

// collect
List nums = [2,5,7,3,8,2,2,1,0,9]
List numsTimesTen = []

// -bad way
nums.each {num ->
    numsTimesTen << num * 10
}
println numsTimesTen

// -- better way
List betterTimesTen = nums.collect {num -> num * 10}
println betterTimesTen

Curry Methods

Currying in Groovy will let you set the value of one parameter of a closure, and it will return a new closure accepting one less argument, think of it as create a clone of a closure and fixing values for some of the parameters, this can be done multiple times as well, here are some examples

Curry method examples
// closure with 3 parameters
def log = { String type, Date createdOn, String msg ->
    println "$createdOn [$type] - $msg"
}
log("DEBUG", new Date(), "This is my first debug statement")  // inoke closure passing 3 arguments

// create a closure using another closure
def debugLog = log.curry("DEBUG")                             // create a new closure but pass 1 argument, this will now be fixed
debugLog(new Date(), "This is another debug statement...")    // so we only need to invoke with 2 arguments
debugLog(new Date(), "This is one more.....")

def todayDebugLog = debugLog.curry(new Date())                // we can continue creating closures fixing parameters
todayDebugLog("This is today's debug msg")                    // thus we only need to pass 1

// right curry
def crazyPersonLog = log.rcurry("Why am I logging this way, right curry (rcurry)")
crazyPersonLog("ERROR",new Date())

// index based currying
def typeMsgLog = log.ncurry(1,new Date())
typeMsgLog("ERROR","This is using ncurry...")

Closure Scope and Delegates

There are three scopes inside a closure

A delegate means you can delegate an action to something else but you have the option to either use the delegate first (DELEGATE_FIRST) or not depending on what opions you use.

Scoping
class ScopeDemo {

    def outerClosure = {
        println this.class.name
        println owner.class.name
        println delegate.class.name
        println "--------------------------"
        
        // Nested closure
        def nestedClosure = {
            println this.class.name         // enclosing class
            println owner.class.name        // points to outer closure
            println delegate.class.name     // points to outer closure
        }
        nestedClosure()
    }

}

def demo = new ScopeDemo()
demo.outerClosure()

Output
------------------------------------------------------------------
uk.co.datadisk.closures.ScopeDemo
uk.co.datadisk.closures.ScopeDemo
uk.co.datadisk.closures.ScopeDemo
--------------------------
uk.co.datadisk.closures.ScopeDemo
uk.co.datadisk.closures.ScopeDemo$_closure1
uk.co.datadisk.closures.ScopeDemo$_closure1
Delegates
def writer = {
    append 'Paul '
    append 'lives in Milton Keynes'
}

// resolves first in scope first (owner first by default)
// override it with the resolveStrategy below
def append(String s){
    println "append() called with argument of $s"
}

// now delegating append to to StringBuffer
StringBuffer sb = new StringBuffer()
writer.resolveStrategy = Closure.DELEGATE_FIRST  // override the default (append above)
writer.delegate = sb                             // the delegate will use the append() method in StringBuffer
println writer()