Finding list/map of free variable(s) of a closure in groovy

857 views Asked by At

This is my simple groovy script;

def fourtify(String str) {

    def clsr = {
         str*4
    }

    return clsr
}

def c = fourtify("aa")
println("binding variables: ${c.getBinding().getVariables()}")
...

All I'm trying to do here is being able to access the free variable "str" using the closure instance to understand how closure works behind the scenes a bit more better. Like, perhaps, Python's locals() method.

Is there a way to do this?

2

There are 2 answers

1
Szymon Stepniak On

The closure you have defined does not store anything in binding object - it simply returns String passed as str variable, repeated 4 times.

This binding object stores all variables that were defined without specifying their types or using def keyword. It is done via Groovy metaprogramming feature (getProperty and setProperty methods to be more specific). So when you define a variable s like:

def clsr = {
     s = str*4
     return s
}

then this closure will create a binding with key s and value evaluated from expression str * 4. This binding object is nothing else than a map that is accessed via getProperty and setProperty method. So when Groovy executes s = str * 4 it calls setProperty('s', str * 4) because variable/property s is not defined. If we make a slightly simple change like:

def clsr = {
     def s = str*4 // or String s = str * 4
     return s
}

then binding s won't be created, because setProperty method does not get executed.

Another comment to your example. If you want to see anything in binding object, you need to call returned closure. In example you have shown above the closure gets returned, but it never gets called. If you do:

def c = fourtify("aa")
c.call()
println("binding variables: ${c.getBinding().getVariables()}")

then your closure gets called and binding object will contain bindings (if any set). Now, if you modify your example to something like this:

def fourtify(String str) {

    def clsr = {
        def n = 4 // it does not get stored as binding
        s = str * n
        return s
    }

    return clsr
}

def c = fourtify("aa")
c.call()
println("binding variables: ${c.getBinding().getVariables()}")

you will see following output in return:

binding variables: [args:[], s:aaaaaaaa]

Hope it helps.

4
daggett On

in your example str is a parameter of the method/function fortify

however maybe following example will give you better Closure understanding:

def c={ String s,int x-> return s*x }

println( c.getClass().getSuperclass() )     // groovy.lang.Closure
println( c.getMaximumNumberOfParameters() ) // 2
println( c.getParameterTypes() )            // [class java.lang.String, int]

the locals() Python's function better matches groovy.lang.Script.getBinding()

and here is a simple example with script:

Script scr = new GroovyShell().parse(''' 
    println this.getBinding().getVariables()  // print "s" and "x"
    z = s*(x+1)                               // declare a new script-level var "z"
    println this.getBinding().getVariables()  // print "s", "x", and "z"
    return s*x 
''')
scr.setBinding( new Binding([
        "s":"ab",
        "x":4
    ]) )
println scr.run() // abababab
println scr.getBinding().getVariables() // print "s", "x", and "z"