This article from a while back points out “the biggest mistake everyone makes with closures” in Ruby, Python, Perl and Javascript.1
Try running this code in Ruby, which counts i
up from 1 to 3 creating a closure each time which returns the value of i
:
a = [] for i in 1..3 a.push(lambda { i }) end for f in a print "#{f.call()} " end
and it prints out the possibly surprising value 3 3 3
.
Using more “functional” constructs doesn’t always save you. This Python also prints all 3s:2
a = [lambda: i for i in xrange(1, 4)] for a in f: print f()
The article goes on to suggest some workarounds.
Here is the equivalent in Swift:
var a: Array<()->Int> = [] for i in 1...3 { a.append( { i }) } for f in a { println("\(f()) ") }
and happily, this prints 1 2 3
.
Note, this also works with references to objects, so if you subsituted a for
over a sequence of objects instead of Int
s in the code above, each closure would capture a reference to a different object.
Be careful though, this applies to for
loops but not to other loops like while
or the C-style for(;;)
, so:
var i: Int = 0 while(++i <= 3) { a.append( { i }) } for f in a { print("\(f()) ") }
prints out 3 3s.
Why is this? Is this a special hack to make for...in
behave less surprisingly? To understand why it does this, you need to realize that this:
for i in 1...3 { a.append( { i }) }
is just a shorthand equivalent to writing this code using the source sequence’s generator:
var g = (1...3).generator() while let i = g.next() a.append( { i }) }
The answer to our question lies in the let i =
, which declares a fresh variable i
for each iteration of the loop, hence if you capture i
, it will retain its value at the end of the block. If you use this form of while
loop, you’ll get the same results as with the for...in
version.