Closures

12.1 Closure Basics

def greet = { name -> "Hello, $name" }
println greet("Ana")
println greet.call("Bo")

def square = { it * it }                 // implicit parameter `it`
def noArgs = { -> "no params" }          // explicitly zero params
def multi = { a, b = 10 -> a + b }       // default parameter
def varargs = { String... args -> args.join(",") }
def typed = { int x, int y -> x * y }
println square(5), multi(1), varargs('a', 'b'), typed(3, 4)
println square.maximumNumberOfParameters, typed.parameterTypes

12.2 Closures Capture Their Environment

def counter = 0
def inc = { counter++ }
3.times { inc() }
println counter                          // 3 (captures by reference)

def makeMultiplier(n) { return { x -> x * n } }
def triple = makeMultiplier(3)
println triple(7)                        // 21

12.3 this, owner, and delegate

The enclosing class instance where the closure is defined

The enclosing object: a class instance or an outer closure

A third-party object used to resolve method calls and properties (defaults to owner). Configurable.

Resolve strategies: OWNER_FIRST (default), DELEGATE_FIRST, OWNER_ONLY, DELEGATE_ONLY, TO_SELF.

Delegation is the foundation of Groovy DSLs and builders (Gradle, Jenkins).

with and tap: cfg.with { host = "x"; port = 1 } (returns the closure's result), new Config().tap { host = "y" } (returns the object).

class Config {
    String host; int port
}
def configure = {
    host = "localhost"                   // resolved against the delegate
    port = 8080
}
def cfg = new Config()
configure.delegate = cfg
configure.resolveStrategy = Closure.DELEGATE_FIRST
configure()
println "$cfg.host:$cfg.port"

12.4 Currying, Composition, Memoization, Trampoline

def add = { a, b -> a + b }
def add5 = add.curry(5)                  // left curry
def rcurried = { a, b -> a - b }.rcurry(1)
def ncurried = { a, b, c -> a + b + c }.ncurry(1, 10)
println add5(3), rcurried(10)

def plus2 = { it + 2 }
def times3 = { it * 3 }
def composed = plus2 >> times3           // times3(plus2(x))
def reversed = plus2 << times3           // plus2(times3(x))
println composed(1), reversed(1)         // 9, 5

def slowFib
slowFib = { n -> n < 2 ? n : slowFib(n - 1) + slowFib(n - 2) }.memoize()
println slowFib(80)
def limited = { it * 2 }.memoizeAtMost(100)

def factorial
factorial = { n, acc = 1G -> n <= 1 ? acc : factorial.trampoline(n - 1, acc * n) }.trampoline()
println factorial(1000).toString().size()   // no StackOverflow

12.5 Closures as Method Arguments

def repeat(int times, Closure action) { times.times { action(it) } }
repeat(3) { println "Iteration $it" }    // trailing closure syntax
def timeIt(Closure c) { def s = System.nanoTime(); c(); println "${(System.nanoTime() - s) / 1e6} ms" }
timeIt { (1..100000).sum() }

12.6 Closures vs Lambdas vs Method References

def c = { x -> x * 2 }                    // Closure
java.util.function.Function<Integer, Integer> f = x -> x * 2   // Java lambda syntax (Groovy 3+)
def ref = String::toUpperCase             // method reference
def ptr = "text".&toUpperCase             // method pointer
Runnable r = { println "SAM coercion" }   // closure → functional interface