2

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?

stdout
  • 2,471
  • 2
  • 31
  • 40

2 Answers2

1

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.

Szymon Stepniak
  • 40,216
  • 10
  • 104
  • 131
  • I see your point but I still fail to see why I don't see the "free" variable "str" anywhere - even after I remove the type String? My point is that variable "str" should be stored somewhere when the script is compiled so when I call the closure along the way, it could resolve that "str" name by looking at, for example, the owner or delegate or sth else. Hope it makes more sense. – stdout Oct 16 '18 at 17:27
1

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"
daggett
  • 26,404
  • 3
  • 40
  • 56
  • This is what I thought in the beginning so that's why I tried to get the variables (including "free" ones) using the getBinding() method on the closure instance. Also, I'm more interested in getting the "free" variables that the closure is closed to once the script it's compiled rather than retrieving information about the parameters. – stdout Oct 16 '18 at 17:13
  • @zgulser, there is no "free" variables for closure. with `delegate` you could access to object that declares this closure and ask for declared class fields with groovy metaclass : `delegate.metaClass.properties.collect{it.name}` or with java native `delegate.getClass().getDeclaredFields()`. however you can't access local variables declared in methods/functions. – daggett Oct 17 '18 at 09:54
  • I don't think this is entirely true - http://groovy-lang.org/closures.html. I know the delegate concept in Groovy but it my case, I think the "str" cannot be part of any delegates as it's a local variable of a function. – stdout Oct 17 '18 at 13:45
  • 1
    That's I want to say. There is no way to get `str` it in your example. Except maybe you want to use AstBuilder to analyze the code... – daggett Oct 17 '18 at 14:14