Skip to content

Instantly share code, notes, and snippets.

@purplefox
Created March 27, 2012 09:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save purplefox/2214446 to your computer and use it in GitHub Desktop.
Save purplefox/2214446 to your computer and use it in GitHub Desktop.
In JavaScript I can do this: (setTimer is just a vertx function which calls the specified handler function at some time (100 ms) in the future.)
var id = vertx.setTimer(100, function() {
stdout.println("id is " + id);
});
This works because the function is a closure, and id is declared (but not initialised) before setTimer is executed.
But if I try to do the same in Groovy it complains that id is not recognised
def id = Vertx.instance.setTimer(100, {
println "is is " + id
})
[junit] groovy.lang.MissingPropertyException: No such property: id for class: core.timer.testClient
I can get it to work in Groovy by declaring id on a separate line, but this is pretty ugly:
def id
id = Vertx.instance.setTimer(100, {
println "is is " + id
})
Just wondering... is this by design? I would expect any local variables declared in the scope before a closure to be accessible in the closure. I understand that id won't be *initialised* before setTimer returns, but does that matter?
@pledbrook
Copy link

Apparently it's by design because Java doesn't allow this. Stuff on the right hand side of an expressions can't see things on the left.

Mind you, the example begs the question why setTimer() doesn't pass in the ID to the function.

@purplefox
Copy link
Author

The stuff in the closure isn't really on the "right hand side" in the normal sense, since the closure is not evaluated when the expression is evaluated, otherwise I would agree with you.

For instance, this clearly makes no sense

long id = 1 + id

But there is no reason why

long id = 1 + foo( { id} )

can't make perfect sense.

I'm interested in why "because Java doesn't allow this" is a reason for not doing it Groovy. After all, Groovy isn't Java, right? ;)

BTW... actually, the timer id is passed into a timer handler, so this isn't a good example. But just think of it as a more general question of accessing return values in closures :)

@osscontributor
Copy link

Part of the problem is that the closure has access to variables that exist where the closure is created.

def id = Vertx.instance.setTimer(100, {
    println "is is " + id
})

...is basically this...

def impl = {
    println "is is " + id
}
def id = Vertx.instance.setTimer(100, impl)

I think in the second example it is more clear that "id" does not exist when the closure is created.

@purplefox
Copy link
Author

There's no reason why a language can't (in principle) make it work as I would expect, after all it works in Ruby and JavaScript. So it's not a question of it not making sense, but rather a question of why Groovy decides to disallow the case.

One possible reason why it might be disallowed is that it would be possible for the closure to see the uninitialised value of id if the closure was executed before the setTimer method had returned. But I'm not sure that's really a big deal.

@melix
Copy link

melix commented Mar 28, 2012

Well, if you are in a script and that you drop the "def", this would work because "id" would then reference a variable in a binding:

id = Vertx.instance.setTimer(100, {
    println "is is " + id
})

Adding "def" in a script means you define a local variable, which indeed in that case may be undefined, as Groovy evaluates RHS before LHS.

@purplefox
Copy link
Author

This begs the question, if it can work for bindings, can't the same semantics be applied to local variables? - i.e. they can have an undefined value if accessed before they are initialised

@blackdrag
Copy link

purplefox, there are different ways of lazy evaluation. Let us assume we do it in a text replacement way. Then if I have x={y}, and execute x right away, I get an error because y is undefined, or it gives a random value. So I instead give x to another method, that has a local variable y and then execute x there... then x will return the value of that local variable in that method. Or you make it bound to the partially limited scope of for example the method. Then x will return a value, as soon as there is an y declared/used somewhere in this method. Doing that even more strict means to use lexical scopes. In those x can access only what has been defined/used before. And that is what Groovy does.

Why did we choose that? Because we wanted code blocks, that behave, if used the same way as in Java, exactly like in Java. And Java has lexical scopes, thus we have lexical scopes for local variables. We could laso have changed the evaluation and do LHS before RHS. Then the variable would be visible, the big difference is then what "Object id=id" means in Java and in Groovy. In Java this is valid code if there is a field id. With evaluating LHS before RHS this code cannot be valid anymore, since the value for id is id, which is not defined. Imagine now "def id = {id}" what do you expect here? "undefined"?

But you are right, these (not having undefined values; do LHS, before RHS; do lexical binding for local variables) are all design decision and could be done different. It is not that Groovy decided to disallow this case, it is simply that there are currently 3 design decisions speaking against this usage. We made the decisions as they are partially because of Java. Groovy is a language of its own, true. But I can give the same argument for not following the Ruby or JavaScript lead here. The reason why we stick tight to Java is mainly, that we want a tight integration with and an easy migration of Java code and Groovy. There are also limitations of the JVM, but they don't really play a rule for this case here, as long as we don't do eval in the first style I described.

@blackdrag
Copy link

One more on the binding... the binding is to be seen as a scope global to the script, that can be set and changed from outside. A local variable on the other side is not visible outside the block it has been defined in.

@purplefox
Copy link
Author

Good answer :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment