Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In order for a groovy closure to modify a variable defined in the scope of a delegate, do you need to explicitly specify delegate.theVariableName?

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'
    }
}
like image 503
Chris Bedford Avatar asked Aug 30 '12 20:08

Chris Bedford


1 Answers

Groovy closures have five strategies for resolving symbols inside closures:

  • OWNER_FIRST: the owner (where the closure is defined) is checked first, then the delegate
  • OWNER_ONLY: the owner is checked, the delegate is only checked if referenced explicitly
  • DELEGATE_FIRST: the delegate is checked first, then the owner
  • DELEGATE_ONLY: the delegate is checked first, the owner is only checked if referenced explicitly
  • TO_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.

like image 84
ataylor Avatar answered Sep 21 '22 14:09

ataylor