Closures - pford68/groovy-examples GitHub Wiki
- Summary
- Comparison to Lambdas
- Syntax
- Invocation
- Parameters
- Closures as Parameters
- Delegation
- Functional Programming
- References
- Notes
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 }
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.
{ <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
returnstatement.
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 // trueParameters 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.
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)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'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.
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
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
thisnotation. - If the closure is defined in a inner class,
thisin 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 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
ownernotation, instead ofgetOwner(). - Again, as with
this, if the closure is defined in a inner class,ownerin the closure will return the inner class, not the top-level one.
The delegate is a powerful concept for building domain specific languages in Groovy. In short, the delegate property lets you set the meaning of this or owner in a closure.
While this and owner refer to the lexical scope of a closure, the delegate is a user-defined object that a closure will use. Assign an object to the delegate property of the closure to set or to change the delegate.
- The delegate of a closure can be accessed by using the
delegateproperty or calling thegetDelegatemethod. - By default, the delegate is set to
owner. - The delegate of a closure can be changed to any object.
- In nested closures, the delegate will correspond to the owner.
class Enclosing { void run() { def cl = { getDelegate() } def cl2 = { delegate } assert cl() == cl2() // true assert cl() == this // true def enclosed = { { -> delegate }.call() } assert enclosed() == enclosed // true: in nested closures, the delegate == the owner } }
Assigning an object to the closure's delegate property achieves the same effect as assigning a function to a property of an object in JavaScript.
If you assign the function to a property of a different object, and invoke the function through that object, then
the meaning of this changes to the new object within the function.
// Let’s create two classes which are not subclasses of each other but both define a property called name:
class Person {
String name
}
class Thing {
String name
}
def p = new Person(name: 'Norman')
def t = new Thing(name: 'Teapot')
// Then let’s define a closure which fetches the name property on the delegate:
def upperCasedName = { delegate.name.toUpperCase() }
// Then by changing the delegate of the closure, you can see that the target object will change:
upperCasedName.delegate = p
assert upperCasedName() == 'NORMAN'
upperCasedName.delegate = t
assert upperCasedName() == 'TEAPOT'The behavior of using a delegate is not much different from having a local variable defined in the lexical scope of the closure:
def target = p
def upperCasedNameUsingVar = { target.name.toUpperCase() }
assert upperCasedNameUsingVar() == 'NORMAN'However, there are major differences:
- in the last example, target is a local variable referenced from within the closure
- the delegate can be used transparently, that is to say without prefixing method calls with delegate.
Whenever, in a closure, a property is accessed without explicitly setting a receiver object, then a delegation strategy is involved. Below, in the cl closure, you see an apparent reference to an undeclared variable name. But since cl's delegate is set to an instance of Person, name is resolved as a property of Person.
class Person {
String name
}
def p = new Person(name:'Igor')
def cl = { name.toUpperCase() }
cl.delegate = p
assert cl() == 'IGOR' The resolveStrategy of Closure determines the order in which Groovy tries to resolve such property references in a Closure.
-
Closure.OWNER_FIRSTis the default strategy. If a property/method exists on the owner, then it will be called on the owner. If not, then the delegate is used. -
Closure.DELEGATE_FIRSTreverses the logic: the delegate is used first, then the owner -
Closure.OWNER_ONLYwill only resolve the property/method lookup on the owner: the delegate will be ignored. -
Closure.DELEGATE_ONLYwill only resolve the property/method lookup on the delegate: the owner will be ignored. -
Closure.TO_SELFcan be used by developers who need advanced meta-programming techniques and wish to implement a custom resolution strategy: the resolution will not be made on the owner or the delegate but only on the closure class itself. It makes only sense to use this if you implement your own subclass of Closure.
In Groovy, "currying" refers to the concept of partial application. It does not correspond to the real concept of currying in functional programming because of the different scoping rules that Groovy applies on closures. 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.
Left currying is the fact of setting the left-most parameter of a closure.
def nCopies = { int n, String str -> str*n }
def twice = nCopies.curry(2)
assert twice('bla') == 'blabla'
assert twice('bla') == nCopies(2, 'bla') Groovy also supports right-currying, using rcurry(), to set the right-most parameter:
def nCopies = { int n, String str -> str*n }
def blah = nCopies.rcurry('bla')
assert blah(2) == 'blabla'
assert blah(2) == nCopies(2, 'bla') Memoization allows the result of the call of a closure to be cached. This is useful if the computation done by a function (closure) is slow, but you know that this function is going to be called often with the same arguments. A typical example is the Fibonacci suite.
def fib
fib = { long n -> n<2?n:fib(n-1)+fib(n-2) }
assert fib(15) == 610 // slow!It is a naive implementation because 'fib' is often called recursively with the same arguments, leading to an exponential algorithm:
- computing fib(15) requires the result of fib(14) and fib(13)
- computing fib(14) requires the result of fib(13) and fib(12)
fib = { long n -> n<2?n:fib(n-1)+fib(n-2) }.memoize()
assert fib(25) == 75025 // fast!The cache works using the actual values of the arguments. This means that you should be very careful if you use memoization with something else than primitive or boxed primitive types.
The behavior of the cache can be tweaked using alternate methods:
-
memoizeAtMostwill generate a new closure which caches at mostnvalues -
memoizeAtLeastwill generate a new closure which caches at leastnvalues -
memoizeBetweenwill generate a new closure which caches at leastnvalues and at mostnvalues
The cache used in all memoize variants is a LRU cache.
Closure composition corresponds to the concept of function composition, creating a new function by composing two or more functions (chaining calls).
def plus2 = { it + 2 }
def times3 = { it * 3 }
def times3plus2 = plus2 << times3
assert times3plus2(3) == 11
assert times3plus2(4) == plus2(times3(4))
def plus2times3 = times3 << plus2
assert plus2times3(3) == 15
assert plus2times3(5) == times3(plus2(5))
// reverse composition
assert times3plus2(3) == (times3 >> plus2)(3)