03 Jan 2013
Closures seemed mystical and confusing. It turns out I have been using them all along.
When working at wrapping my head around closures, I found out that they have to do with lexical scope. I had seen that term a few times, was unsure of what it meant and this was a perfect time to finally look this up. It turns out to answer it, you have to ask a question first.
What does it mean to be in a function?
It turns out that there are two different ways to look at this. You can look at it through lexical aka. static scope(which is what we are usually dealing with) or dynamic scope. Let's read this bit from wikipedia...
In lexical scoping (or lexical scope; also called static scoping or static scope), if a variable name's scope is a certain function, then its scope is the program text of the function definition: within that text, the variable name exists, and is bound to its variable, but outside that text, the variable name does not exist. By contrast, in dynamic scoping (or dynamic scope), if a variable name's scope is a certain function, then its scope is the time-period during which the function is executing: while the function is running, the variable name exists, and is bound to its variable, but after the function returns, the variable name does not exist.
Lexical scoping is most used and that's what we will be referring to in the rest of this post.
Let's take a look at some Python!(this is also a wikipedia example)
1 def counter(): 2 x = 0 3 def increment(y): 4 nonlocal x 5 x += y 6 print(x) 7 return increment 8 9 counter1_increment = counter() 10 counter2_increment = counter() 11 12 counter1_increment(1) # prints 1 13 counter1_increment(7) # prints 8 14 counter2_increment(1) # prints 1 15 counter1_increment(1) # prints 9
Now, this confused me at first because when I was looking at
1 counter1_increment = counter() 2 counter2_increment = counter()
I was imagining
1 counter1_increment = def counter(): 2 x = 0 3 def increment(y): 4 nonlocal x 5 x += y 6 print(x) 7 return increment 8 9 counter2_increment = def counter(): 10 x = 0 11 def increment(y): 12 nonlocal x 13 x += y 14 print(x) 15 return increment
Looking at it that way, the math was not adding up, neither was my understanding of the closure. Read carefully because what those assignments are really doing is this....
1 counter1_increment = def increment(y): 2 nonlocal x 3 x += y 4 print(x) 5 6 counter2_increment = def increment(y): 7 nonlocal x 8 x += y 9 print(x)
Therefore, when you make these calls...
1 counter1_increment(1) # prints 1 2 counter1_increment(7) # prints 8 3 counter2_increment(1) # prints 1 4 counter1_increment(1) # prints 9
The math actually adds up. The first time through in counter1_increment(1), the 1 is assigned to y. nonlocal x is 0 and 0+1= 1. 1 is assigned to x and printed. The second time through, 7 is assigned to y. Since x belongs to the counter scope, it is outside of the increment scope. However, since counter scope "closes over" the increment scope, increment still has access to x, creating a closure. This is powerful because it is a way to maintain a private state and use values in places that would otherwise be out of scope.
Another place you have seen this before is in ruby's .each and in nested functions in clojure(many times anonymous although they can also be named).
I am really excited to understand this concept. I hope this helps you out!