Closures
This chapter covers Groovy Closures. A closure in Groovy is an open, anonymous, block of code that can take arguments,
return a value and be assigned to a variable. A closure may reference variables declared in its surrounding scope. In
opposition to the formal definition of a closure, Closure
in the Groovy language can also contain free variables which
are defined outside of its surrounding scope. While breaking the formal concept of a closure, it offers a variety of
advantages which are described in this chapter.
1. Syntax
1.1. Defining a closure
A closure definition follows this syntax:
{ [closureParameters -> ] statements }
Where [closureParameters->]
is an optional comma-delimited list of
parameters, and statements are 0 or more Groovy statements. The parameters
look similar to a method parameter list, and these parameters may be
typed or untyped.
When a parameter list is specified, the ->
character
is required and serves to separate the arguments from the closure body.
The statements portion consists of 0, 1, or many Groovy statements.
Some examples of valid closure definitions:
{ item } (1)
{ -> item } (2)
{ println it } (3)
{ it -> println it } (4)
{ name -> println name } (5)
{ String x, int y -> (6)
println "hey ${x} the value is ${y}"
}
{ reader -> (7)
def line = reader.readLine()
line.trim()
}
1 | A closure referencing a variable named item |
2 | It is possible to explicitly separate closure parameters from code by adding an arrow (-> ) |
3 | A closure using an implicit parameter (it ) |
4 | An alternative version where it is an explicit parameter |
5 | In that case it is often better to use an explicit name for the parameter |
6 | A closure accepting two typed parameters |
7 | A closure can contain multiple statements |
1.2. Closures as an object
A closure is an instance of the groovy.lang.Closure
class, making it assignable to a variable or a field as any
other variable, despite being a block of code:
def listener = { e -> println "Clicked on $e.source" } (1)
assert listener instanceof Closure
Closure callback = { println 'Done!' } (2)
Closure<Boolean> isTextFile = {
File it -> it.name.endsWith('.txt') (3)
}
1 | You can assign a closure to a variable, and it is an instance of groovy.lang.Closure |
2 | If not using def or var , use groovy.lang.Closure as the type |
3 | Optionally, you can specify the return type of the closure by using the generic type of groovy.lang.Closure |
1.3. Calling a closure
A closure, as an anonymous block of code, can be called like any other method. If you define a closure which takes no argument like this:
def code = { 123 }
Then the code inside the closure will only be executed when you call the closure, which can be done by using the variable as if it was a regular method:
assert code() == 123
Alternatively, you can be explicit and use the call
method:
assert code.call() == 123
The principle is the same if the closure accepts arguments:
def isOdd = { int i -> i%2 != 0 } (1)
assert isOdd(3) == true (2)
assert isOdd.call(2) == false (3)
def isEven = { it%2 == 0 } (4)
assert isEven(3) == false (5)
assert isEven.call(2) == true (6)
1 | define a closure which accepts an int as a parameter |
2 | it can be called directly |
3 | or using the call method |
4 | same goes for a closure with an implicit argument (it ) |
5 | which can be called directly using (arg) |
6 | or using call |
Unlike a method, a closure always returns a value when called. The next section discusses how to declare closure arguments, when to use them and what is the implicit "it" parameter.
2. Parameters
2.1. Normal parameters
Parameters of closures follow the same principle as parameters of regular methods:
-
an optional type
-
a name
-
an optional default value
Parameters are separated with commas:
def closureWithOneArg = { str -> str.toUpperCase() }
assert closureWithOneArg('groovy') == 'GROOVY'
def closureWithOneArgAndExplicitType = { String str -> str.toUpperCase() }
assert closureWithOneArgAndExplicitType('groovy') == 'GROOVY'
def closureWithTwoArgs = { a,b -> a b }
assert closureWithTwoArgs(1,2) == 3
def closureWithTwoArgsAndExplicitTypes = { int a, int b -> a b }
assert closureWithTwoArgsAndExplicitTypes(1,2) == 3
def closureWithTwoArgsAndOptionalTypes = { a, int b -> a b }
assert closureWithTwoArgsAndOptionalTypes(1,2) == 3
def closureWithTwoArgAndDefaultValue = { int a, int b=2 -> a b }
assert closureWithTwoArgAndDefaultValue(1) == 3
2.2. Implicit parameter
When a closure does not explicitly define a parameter list (using ->
), a closure always defines an implicit
parameter, named it
. This means that this code:
def greeting = { "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'
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:
def magicNumber = { -> 42 }
// this call will fail because the closure doesn't accept any argument
magicNumber(11)
2.3. Varargs
It is possible for a closure to declare variable arguments like any other method. Vargs methods are methods that can accept a variable number of arguments if the last parameter is of variable length (or an array) like in the next examples:
def concat1 = { String... args -> args.join('') } (1)
assert concat1('abc','def') == 'abcdef' (2)
def concat2 = { String[] args -> args.join('') } (3)
assert concat2('abc', 'def') == 'abcdef'
def multiConcat = { int n, String... args -> (4)
args.join('')*n
}
assert multiConcat(2, 'abc','def') == 'abcdefabcdef'
1 | A closure accepting a variable number of strings as first parameter |
2 | It may be called using any number of arguments without having to explicitly wrap them into an array |
3 | The same behavior is directly available if the args parameter is declared as an array |
4 | As long as the last parameter is an array or an explicit vargs type |
3. Delegation strategy
3.1. Groovy closures vs lambda expressions
Groovy defines closures as instances of the Closure class. It makes it very different from lambda expressions in Java 8. 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.
3.2. Owner, delegate and this
To understand the concept of delegate, we must first explain the meaning of this
inside a closure. A closure actually
defines 3 distinct things:
-
this
corresponds to the enclosing class where the closure is defined -
owner
corresponds to the enclosing object where the closure is defined, which may be either a class or a closure -
delegate
corresponds to a third party object where methods calls or properties are resolved whenever the receiver of the message is not defined
3.2.1. The meaning of this
In a closure, calling getThisObject
will return the enclosing class where the closure is defined. It is equivalent to
using an explicit this
:
class Enclosing {
void run() {
def whatIsThisObject = { getThisObject() } (1)
assert whatIsThisObject() == this (2)
def whatIsThis = { this } (3)
assert whatIsThis() == this (4)
}
}
class EnclosedInInnerClass {
class Inner {
Closure cl = { this } (5)
}
void run() {
def inner = new Inner()
assert inner.cl() == inner (6)
}
}
class NestedClosures {
void run() {
def nestedClosures = {
def cl = { this } (7)
cl()
}
assert nestedClosures() == this (8)
}
}
1 | a closure is defined inside the Enclosing class, and returns getThisObject |
2 | calling the closure will return the instance of Enclosing where the closure is defined |
3 | in general, you will just want to use the shortcut this notation |
4 | and it returns exactly the same object |
5 | if the closure is defined in an inner class |
6 | this in the closure will return the inner class, not the top-level one |
7 | in case of nested closures, like here cl being defined inside the scope of nestedClosures |
8 | then this corresponds to the closest outer class, not the enclosing closure! |
It is of course possible to call methods from the enclosing class this way:
class Person {
String name
int age
String toString() { "$name is $age years old" }
String dump() {
def cl = {
String msg = this.toString() (1)
println msg
msg
}
cl()
}
}
def p = new Person(name:'Janice', age:74)
assert p.dump() == 'Janice is 74 years old'
1 | the closure calls toString on this , which will actually call the toString method on the enclosing object,
that is to say the Person instance |
3.2.2. Owner of a closure
The owner of a closure is very similar to the definition of this in a closure with a subtle difference: it will return the direct enclosing object, be it a closure or a class:
class Enclosing {
void run() {
def whatIsOwnerMethod = { getOwner() } (1)
assert whatIsOwnerMethod() == this (2)
def whatIsOwner = { owner } (3)
assert whatIsOwner() == this (4)
}
}
class EnclosedInInnerClass {
class Inner {
Closure cl = { owner } (5)
}
void run() {
def inner = new Inner()
assert inner.cl() == inner (6)
}
}
class NestedClosures {
void run() {
def nestedClosures = {
def cl = { owner } (7)
cl()
}
assert nestedClosures() == nestedClosures (8)
}
}
1 | a closure is defined inside the Enclosing class, and returns getOwner |
2 | calling the closure will return the instance of Enclosing where the closure is defined |
3 | in general, you will just want to use the shortcut owner notation |
4 | and it returns exactly the same object |
5 | if the closure is defined in an inner class |
6 | owner in the closure will return the inner class, not the top-level one |
7 | but in case of nested closures, like here cl being defined inside the scope of nestedClosures |
8 | then owner corresponds to the enclosing closure, hence a different object from this ! |
3.2.3. Delegate of a closure
The delegate of a closure can be accessed by using the delegate
property or calling the getDelegate
method. It is a
powerful concept for building domain specific languages in Groovy. While this and owner
refer to the lexical scope of a closure, the delegate is a user defined object that a closure will use. By default, the
delegate is set to owner
:
class Enclosing {
void run() {
def cl = { getDelegate() } (1)
def cl2 = { delegate } (2)
assert cl() == cl2() (3)
assert cl() == this (4)
def enclosed = {
{ -> delegate }.call() (5)
}
assert enclosed() == enclosed (6)
}
}
1 | you can get the delegate of a closure calling the getDelegate method |
2 | or using the delegate property |
3 | both return the same object |
4 | which is the enclosing class or closure |
5 | in particular in case of nested closures |
6 | delegate will correspond to the owner |
The delegate of a closure can be changed to any object. Let’s illustrate this by creating 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'
At this point, the behavior is not different from having a target
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.
as explained in the next paragraph.
3.2.4. Delegation strategy
Whenever, in a closure, a property is accessed without explicitly setting a receiver object, then a delegation strategy is involved:
class Person {
String name
}
def p = new Person(name:'Igor')
def cl = { name.toUpperCase() } (1)
cl.delegate = p (2)
assert cl() == 'IGOR' (3)
1 | name is not referencing a variable in the lexical scope of the closure |
2 | we can change the delegate of the closure to be an instance of Person |
3 | and the method call will succeed |
The reason this code works is that the name
property will be resolved transparently on the delegate
object! This is
a very powerful way to resolve properties or method calls inside closures. There’s no need to set an explicit delegate.
receiver: the call will be made because the default delegation strategy of the closure makes it so. A closure actually
defines multiple resolution strategies that you can choose:
-
Closure.OWNER_FIRST
is 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_FIRST
reverses the logic: the delegate is used first, then the owner -
Closure.OWNER_ONLY
will only resolve the property/method lookup on the owner: the delegate will be ignored. -
Closure.DELEGATE_ONLY
will only resolve the property/method lookup on the delegate: the owner will be ignored. -
Closure.TO_SELF
can 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 ofClosure
.
Let’s illustrate the default "owner first" strategy with this code:
class Person {
String name
def pretty = { "My name is $name" } (1)
String toString() {
pretty()
}
}
class Thing {
String name (2)
}
def p = new Person(name: 'Sarah')
def t = new Thing(name: 'Teapot')
assert p.toString() == 'My name is Sarah' (3)
p.pretty.delegate = t (4)
assert p.toString() == 'My name is Sarah' (5)
1 | for the illustration, we define a closure member which references "name" |
2 | both the Person and the Thing class define a name property |
3 | Using the default strategy, the name property is resolved on the owner first |
4 | so if we change the delegate to t which is an instance of Thing |
5 | there is no change in the result: name is first resolved on the owner of the closure |
However, it is possible to change the resolution strategy of the closure:
p.pretty.resolveStrategy = Closure.DELEGATE_FIRST
assert p.toString() == 'My name is Teapot'
By changing the resolveStrategy
, we are modifying the way Groovy will resolve the "implicit this" references: in this
case, name
will first be looked in the delegate, then if not found, on the owner. Since name
is defined in the
delegate, an instance of Thing
, then this value is used.
The difference between "delegate first" and "delegate only" or "owner first" and "owner only" can be illustrated if one of the delegate (resp. owner) does not have such a method or property:
class Person {
String name
int age
def fetchAge = { age }
}
class Thing {
String name
}
def p = new Person(name:'Jessica', age:42)
def t = new Thing(name:'Printer')
def cl = p.fetchAge
cl.delegate = p
assert cl() == 42 (1)
cl.delegate = t
assert cl() == 42 (1)
cl.resolveStrategy = Closure.DELEGATE_ONLY
cl.delegate = p
assert cl() == 42 (2)
cl.delegate = t
try {
cl() (3)
assert false
} catch (MissingPropertyException ex) {
// "age" is not defined on the delegate
}
1 | for "owner first" it doesn’t matter what the delegate is |
2 | for "delegate only" having p as the delegate succeeds |
3 | for "delegate only" having t as the delegate fails |
In this example, we define two classes which both have a name
property but only the Person
class declares an age
.
The Person
class also declares a closure which references age
. We can change the default resolution strategy from
"owner first" to "delegate only". Since the owner of the closure is the Person
class, then we can check that if the
delegate is an instance of Person
, calling the closure is successful, but if we call it with a delegate being an
instance of Thing
, it fails with a groovy.lang.MissingPropertyException
. Despite the closure being defined inside
the Person
class, the owner is not used.
A comprehensive explanation about how to use this feature to develop DSLs can be found in a dedicated section of the manual. |
3.2.5. Delegation strategy in the presence of metaprogramming
When describing the "owner first" delegation strategy we spoke about using a property/method from the owner if it "existed" otherwise using the respective property/method from the delegate. And a similar story for "delegate first" but in reverse. Instead of using the word "existed", it would have been more accurate to use the wording "handled". That means that for "owner first", if the property/method exists in the owner, or it has a propertyMissing/methodMissing hook, then the owner will handle the member access.
We can see this in action with a slightly altered version of our previous example:
class Person {
String name
int age
def fetchAge = { age }
}
class Thing {
String name
def propertyMissing(String name) { -1 }
}
def p = new Person(name:'Jessica', age:42)
def t = new Thing(name:'Printer')
def cl = p.fetchAge
cl.resolveStrategy = Closure.DELEGATE_FIRST
cl.delegate = p
assert cl() == 42
cl.delegate = t
assert cl() == -1
In this example, even though our instance of the Thing
class (our delegate for the last use of cl
) has no age
property,
the fact that it handles the missing property via its propertyMissing
hook,
means that age
will be -1
.
4. Closures in GStrings
Take the following code:
def x = 1
def gs = "x = ${x}"
assert gs == 'x = 1'
The code behaves as you would expect, but what happens if you add:
x = 2
assert gs == 'x = 2'
You will see that the assert fails! There are two reasons for this:
-
a GString only evaluates lazily the
toString
representation of values -
the syntax
${x}
in a GString does not represent a closure but an expression to$x
, evaluated when the GString is created.
In our example, the GString
is created with an expression referencing x
. When the GString
is created, the value
of x
is 1, so the GString
is created with a value of 1. When the assert is triggered, the GString
is evaluated
and 1 is converted to a String
using toString
. When we change x
to 2, we did change the value of x
, but it is
a different object, and the GString
still references the old one.
A GString will only change its toString representation if the values it references are mutating. If the references
change, nothing will happen.
|
If you need a real closure in a GString and for example enforce lazy evaluation of variables, you need to use the
alternate syntax ${⊟ x}
like in the fixed example:
def x = 1
def gs = "x = ${-> x}"
assert gs == 'x = 1'
x = 2
assert gs == 'x = 2'
And let’s illustrate how it differs from mutation with this code:
class Person {
String name
String toString() { name } (1)
}
def sam = new Person(name:'Sam') (2)
def lucy = new Person(name:'Lucy') (3)
def p = sam (4)
def gs = "Name: ${p}" (5)
assert gs == 'Name: Sam' (6)
p = lucy (7)
assert gs == 'Name: Sam' (8)
sam.name = 'Lucy' (9)
assert gs == 'Name: Lucy' (10)
1 | the Person class has a toString method returning the name property |
2 | we create a first Person named Sam |
3 | we create another Person named Lucy |
4 | the p variable is set to Sam |
5 | and a closure is created, referencing the value of p , that is to say Sam |
6 | so when we evaluate the string, it returns Sam |
7 | if we change p to Lucy |
8 | the string still evaluates to Sam because it was the value of p when the GString was created |
9 | so if we mutate Sam to change the name to Lucy |
10 | this time the GString is correctly mutated |
So if you don’t want to rely on mutating objects or wrapping objects, you must use closures in GString
by explicitly
declaring an empty argument list:
class Person {
String name
String toString() { name }
}
def sam = new Person(name:'Sam')
def lucy = new Person(name:'Lucy')
def p = sam
// Create a GString with lazy evaluation of "p"
def gs = "Name: ${-> p}"
assert gs == 'Name: Sam'
p = lucy
assert gs == 'Name: Lucy'
5. Closure coercion
Closures can be converted into interfaces or single-abstract method types. Please refer to this section of the manual for a complete description.
6. Functional programming
Closures, like lambda expressions in Java 8 are at the core of the functional programming paradigm in Groovy. Some functional programming
operations on functions are available directly on the Closure
class, like illustrated in this section.
6.1. Currying
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.
6.1.1. Left currying
Left currying is the fact of setting the left-most parameter of a closure, like in this example:
def nCopies = { int n, String str -> str*n } (1)
def twice = nCopies.curry(2) (2)
assert twice('bla') == 'blabla' (3)
assert twice('bla') == nCopies(2, 'bla') (4)
1 | the nCopies closure defines two parameters |
2 | curry will set the first parameter to 2 , creating a new closure (function) which accepts a single String |
3 | so the new function call be called with only a String |
4 | and it is equivalent to calling nCopies with two parameters |
6.1.2. Right currying
Similarly to left currying, it is possible to set the right-most parameter of a closure:
def nCopies = { int n, String str -> str*n } (1)
def blah = nCopies.rcurry('bla') (2)
assert blah(2) == 'blabla' (3)
assert blah(2) == nCopies(2, 'bla') (4)
1 | the nCopies closure defines two parameters |
2 | rcurry will set the last parameter to bla , creating a new closure (function) which accepts a single int |
3 | so the new function call be called with only an int |
4 | and it is equivalent to calling nCopies with two parameters |
6.1.3. Index based currying
In case a closure accepts more than 2 parameters, it is possible to set an arbitrary parameter using ncurry
:
def volume = { double l, double w, double h -> l*w*h } (1)
def fixedWidthVolume = volume.ncurry(1, 2d) (2)
assert volume(3d, 2d, 4d) == fixedWidthVolume(3d, 4d) (3)
def fixedWidthAndHeight = volume.ncurry(1, 2d, 4d) (4)
assert volume(3d, 2d, 4d) == fixedWidthAndHeight(3d) (5)
1 | the volume function defines 3 parameters |
2 | ncurry will set the second parameter (index = 1) to 2d , creating a new volume function which accepts length and height |
3 | that function is equivalent to calling volume omitting the width |
4 | it is also possible to set multiple parameters, starting from the specified index |
5 | the resulting function accepts as many parameters as the initial one minus the number of parameters set by ncurry |
6.2. Memoization
Memoization allows the result of the call of a closure to be cached. It is interesting 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. A naive implementation may look like this:
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 offib(14)
andfib(13)
-
computing
fib(14)
requires the result offib(13)
andfib(12)
Since calls are recursive, you can already see that we will compute the same values again and again, although they could
be cached. This naive implementation can be "fixed" by caching the result of calls using memoize
:
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:
-
memoizeAtMost
will generate a new closure which caches at most n values -
memoizeAtLeast
will generate a new closure which caches at least n values -
memoizeBetween
will generate a new closure which caches at least n values and at most n values
The cache used in all memoize variants is an LRU cache.
6.3. Composition
Closure composition corresponds to the concept of function composition, that is to say creating a new function by composing two or more functions (chaining calls), as illustrated in this example:
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)
6.4. Trampoline
Recursive algorithms are often restricted by a physical limit: the maximum stack height. For example, if you call a method
that recursively calls itself too deep, you will eventually receive a StackOverflowException
.
An approach that helps in those situations is by using Closure
and its trampoline capability.
Closures are wrapped in a TrampolineClosure
. Upon calling, a trampolined Closure
will call the original Closure
waiting
for its result. If the outcome of the call is another instance of a TrampolineClosure
, created perhaps as a result
to a call to the trampoline()
method, the Closure
will again be invoked. This repetitive invocation of returned
trampolined Closures instances will continue until a value other than a trampolined Closure
is returned. That value
will become the final result of the trampoline. That way, calls are made serially, rather than filling the stack.
Here’s an example of the use of trampoline()
to implement the factorial function:
def factorial
factorial = { int n, def accu = 1G ->
if (n < 2) return accu
factorial.trampoline(n - 1, n * accu)
}
factorial = factorial.trampoline()
assert factorial(1) == 1
assert factorial(3) == 1 * 2 * 3
assert factorial(1000) // == 402387260.. plus another 2560 digits
6.5. Method pointers
It is often practical to be able to use a regular method as a closure. For example, you might want to use the currying abilities of a closure, but those are not available to normal methods. In Groovy, you can obtain a closure from any method with the method pointer operator.