Languages: Lua Level: Advanced
Some of you are groaning at the title. My hope is that, by the end of this essay, I will have you all groaning. }:->
A closure is a tricky idea, and I often find myself looking it up to get a grasp on exactly what it is. Wikipedia says that a closure is "...a function that is evaluated in an environment containing one or more bound variables." That's a good, simple definition, but it doesn't explain very well what's going on. To show you closures, I'm going to use Lua again. Lua's simple syntax and first-class anonymous functions make it an ideal language for describing closures. If you don't know Lua or don't have it handy, you will probably still be able to follow along.
Let's start with a simple example of a closure that returns a series of ever-increasing integers:
function f()
local x = 0
return function()
x = x + 1
return x
end
end
g = f()
The function f returns a function that we call multiple times to get our series of integers. Now it would seem to the naïve programmer that x will have gone out of scope after f returns, which will cause a runtime error. But lo, and behold:
> =g() 1 > =g() 2 > =g() 3
(=g() is Lua for "print the value of g().")
Our phantom variable x is alive and well! What happened here? Well, to start off with, we must understand scope.
Each block in Lua has a symbol table that holds local variables. A block is something that begins with function, if, while and friends, and ends with end. When a variable is referenced in a block, Lua first looks for the variable in the current block. If it can't find it there, it looks in the block enclosing the current block. It keeps going in this way all the way up to global scope. The scope of a variable, then, is the extent to which it can be "seen."
Next, we must understand the idea of bound variables. When a variable is referenced, the current scope is searched for a variable with that name. If it is found, the variable is bound in that expression. A variable that is not bound is called a free variable. For example:
a = 3
function f(b)
return a + b + c
end
In this function f, variables a and b are bound to a in global scope and the argument b to the function. Variable c, however, is free because no variable with that name exists when the function is defined. When f is run, it will look for c in the global scope and bind that value to the variable, or else fail. You can think of a free variable as a variable that is "to be announced."
(Aside: In Lua, variables that are not defined have the special value nil. The above code will error when c is not defined because it will attempt addition with a nil value, but if we were simply doing return c, it would not. Keep in mind that Lua is special in this way, and other languages like Javascript or Python will produce an error whenever free variables cannot be bound.)
In our closure, then, the secret is that the x defined in f() is bound to x inside the anonymous function, and thus continues to exist after f() returns. This anonymous function carries around its bound variable x, and voila! A closure.
So what are closures good for? Well, one of the fun things about Lua is that you can give for a function that returns an anonymous function, typically a closure, and for will call that anonymous function once per loop until it returns nil. This kind of function is called an iterator in Lua (though it is more correctly called a generator — the anonymous function that for calls is the proper iterator). Suppose we want to iterate over a sin wave from one point to another. We could write this:
for i = 0, math.pi*2, 0.1 do
x = math.sin(i)
-- Do something with x
done
But it's not exactly clear from the loop definition what's going on. With an iterator, we can make this loop look a bit more self explanatory.
function sin_seq(from, to, step)
local x = from - step
return function()
x = x + step
if x < to then
return math.sin(x)
else
return nil
end
end
end
for x in sin_seq(0, math.pi*2, 0.1) do
-- Do something with x
end
Yes, the definition of sin_seq is a bit long and complicated, but it's much clearer what's going on in your loop, which is important when you look over your code six months down the road.
And that is what closures are all about. Commence groaning. :)