I stumbled onto something with Groovy closures and delegates that I'm not sure is an official part of the language or perhaps even a bug.
Basically I am defining a closure that I read in as a string from an external source, and one of the variables in the class that defines the closure needs to be modified by the closure. I wrote a simple example showing what I found works, and what does not work.
If you look at the test code below you will see a class that defines a variable
animal = "cat"
and two closures defined on the fly from strings that attempt to modify the animal variable.
This works >
String code = "{ -> delegate.animal = 'bear'; return name + 'xx' ; }"
But this does not
String code = "{ -> animal = 'bear'; return name + 'xx' ; }"
It seems like I need to explicitly qualify my to-be-modified variable with 'delegate.' for this to work. (I guess i can also define a setter in the enclosing class for the closure to call to modify the value.)
So.... I've found out how to make this work, but I'd be interested if someone could point me to some groovy doc that explains the rules behind this.
Specifically.... why will the simple assignment
animal = 'bear'
affect the original variable ? Are there shadow copies being made here or something ?
import org.junit.Test
/*
* Author: cbedford
* Date: 8/30/12
* Time: 1:16 PM
*/
class GroovyTest {
String animal = "cat"
String name = "fred"
@Test
public void testDelegateWithModificationOfDelegateVariable() {
String code = "{ -> delegate.animal = 'bear'; return name + 'xx' ; }"
def shell = new GroovyShell()
def closure = shell.evaluate(code)
closure.delegate = this
def result = closure()
println "result is $result"
println "animal is $animal"
assert animal == 'bear'
assert result == 'fredxx'
}
// This test will FAIL.
@Test
public void testDelegateWithFailedModificationOfDelegateVariable() {
String code = "{ -> animal = 'bear'; return name + 'xx' ; }"
def shell = new GroovyShell()
def closure = shell.evaluate(code)
closure.delegate = this
def result = closure()
println "result is $result"
println "animal is $animal"
assert animal == 'bear'
assert result == 'fredxx'
}
}
Groovy closures have five strategies for resolving symbols inside closures:
OWNER_FIRST
: the owner (where the closure is defined) is checked first, then the delegateOWNER_ONLY
: the owner is checked, the delegate is only checked if referenced explicitlyDELEGATE_FIRST
: the delegate is checked first, then the ownerDELEGATE_ONLY
: the delegate is checked first, the owner is only checked if referenced explicitlyTO_SELF
: neither delegate nor owner are checked The default is OWNER_FIRST
. Since the closure is defined dynamically, your owner is a Script object which has special rules itself. Writing animal = 'bear'
in a Script will actually create a new binding called animal
and assign 'bear'
to it.
You can fix your tests to work without explicitly referencing delegate by simply changing the resolve strategy on the closure before calling it with:
closure.resolveStrategy = Closure.DELEGATE_FIRST
This will avoid the odd the Script binding and use the delegate as expected.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With