Interesting Groovy Loops Detail Tuesday, August 07, 2007

While fixing a bug in Grails last night I came across something that I found interesting. At first I was really puzzled but now that I know what is going on, it all makes perfect sense.

A unit test that had been passing for some time just started failing. I isolated a specific revision of a specific file that seemed to be causing the test to fail. The test passes with version X of the file and the test fails with version X+1 of that file. I spent some time diffing the 2 revisions and couldn't find anything that seemed relevant. Eventually I figured out the specific change that was causing the failure. There was code that looked something like this...


someCollection.each { someElement ->
// do something with someElement
}


That code had been changed to look something like this...


for(someElement in someCollection) {
// do something with someElement
}


The code inside the loop was exactly the same for both versions. The only difference is the original code was using "each" to iterate over the collection and the new code was using a "for" loop to do the same thing. I puzzled over that for a bit trying to figure out what was going wrong and after discussing with my co-developers the problem eventually became clear. Code inside the loop was making use of someElement inside of a closure and later that closure was being executed. Consider this...


def listOfClosures = []
def numbers = [1, 2, 3]
numbers.each { number ->
listOfClosures[number] = { println "number is $number" }
}
listOfClosures[1]()
listOfClosures[2]()
listOfClosures[3]()


As you might guess, the output from that looks like this...


number is 1
number is 2
number is 3


Now consider this...


def listOfClosures = []
def numbers = [1, 2, 3]
for (number in numbers) {
listOfClosures[number] = { println "number is $number" }
}
listOfClosures[1]()
listOfClosures[2]()
listOfClosures[3]()


The output from that looks like this...


number is 3
number is 3
number is 3


With the "for" loop approach, the same "number" variable is used each time through the loop so by the time the loop completes, the value of "number" is 3 which is what is used by the time the closures execute. With the "each" approach, a separate variable is used for each iteration so each closure accesses the "number" that was around when the closure was declared. None of that is broken or a bug. I believe it is all working as it should.

Thanks to Charles Nutter and Graeme Rocher for helping with that.