Using closures in Lua to avoid global variables for callbacks

Share on Facebook0Share on Google+0Tweet about this on TwitterShare on LinkedIn0

This type of question has appeared on the forums multiple times.
Basically, it goes something like this:

I have set up a listener callback function. But when the callback function is invoked, I would really like access to a specific variable in that callback function. The event that is passed back to me doesn’t give me access to the variable I want. How can I access my variable?

There are multiple solutions to this problem, but often using a global variable is the path of least resistance. But nobody really likes being forced to use global variables when they don’t have to. Well, I’m going to introduce a much more elegant solution that leverages the true power of Lua. If you read Programming in Lua, the solution comes directly from Chapter 6.1 Closures.

So let’s take a more concrete example.

One of our new examples for our new audio API is called SimpleMixer. Among other things, the example features individual sliders to control separate channel volumes independently.

So let’s start with some simplified code to get the feel of how this is going to work. We want a callback function to handle slider events. We will use this to change an individual channel volume.

[cc lang=”lua”]
function onSlider( event )
local new_volume = event.value
— will call audio.setVolume(new_volume, { channel = ??? } )

We create 4 sliders in a function called makeSliders. (Most code omitted for brevity.)

[cc lang=”lua”]
function makeSliders()
— create 4 sliders
for i=1, 4 do
local new_slider = slider.newSlider{ onEvent = onSlider }

Now you should be able to see the basic problem. When onSlider is called, we don’t know which channel is associated with the slider the the function was invoked for. So we don’t know which channel to specify in the audio.setVolume() call.

So let’s jump directly to the solution:
[cc lang=”lua”]
function createSliderCallback( which_channel )
local audioChannel = which_channel
return function ( event )
local new_volume = event.value
audio.setVolume(new_volume, { channel = audioChannel } )

function makeSliders()
— create 4 sliders
for i=1, 4 do
local new_slider = slider.newSlider{ onEvent = createSliderCallback( i ) }

When setting the onEvent callback function, instead of directly passing back a callback function, we now invoke a new intermediate function called createSliderCallback whose job is to return the final callback function.

If we examine the createSliderCallback function, there are several things to notice:

  • It returns an anonymous function which was formerly our onSlider() function.
  • The anonymous function uses audioChannel as the channel for audio.setVolume()

For those who don’t know about closures, there are two basic concepts going on here. The first is that in Lua, functions are first-class values. This means functions can be used in the same manner as other first-class values such as numbers and strings. They can be stored in variables and tables, can be passed as arguments, and be returned by other functions. In this example, we are returning a function which is perfectly fine for first-class values.

The second concept is lexical scoping. This means that functions can access variables of their enclosing functions. So in this example, even though our variable audioChannel is declared outside our anonymous function (formerly known as onSlider()), it can still access that variable.

This is probably not that surprising since we see this all the time, particularly with global variables. But the subtle and interesting part is that audioChannel is declared as a local variable in createSliderCallback(), so this variable cannot be accessed by anything else except createSliderCallback and our anonymous function. The other thing that makes this interesting and subtle is that our anonymous function is returned and will continued to be used even after the enclosing function no longer has any use for us. And that anonymous function will still be able to access the audioChannel variable.

This anonymous function in this example is a closure. A closure is a function that accesses one or more local variables from its enclosing environment.

So to sum up how the above code works:
In makeSliders(), for each new slider we create, when we set the onEvent listener, we essentially generate a new instance of a function. We pass a audio channel number to the intermediate function which gets saved in the local variable, ‘audioChannel’. Thus every slider listener will have a unique channel number. So when the callback is invoked, we have a reference to the channel that needs to be invoked for setVolume().

The solution is elegant because:

  • It is 100% pure Lua (doesn’t require special API support in Corona)
  • Doesn’t require global variables
  • Is generalizable to many different problems

Functions as first-class values and closures are powerful features in Lua that many people coming from languages that lack these features often overlook or forget about. We hope this post gives you some new ideas on how to approach problems in Lua and may help you write shorter, simpler, more elegant, and more productive code.

For additional reading:

Additional Thoughts For Object-Oriented programmers

If you are familiar with object-oriented programming, this might help you conceptualize closures a little better. (If you aren’t familiar with object-oriented programming, feel free to skip this.)

Notice that in the above example, we achieved the following:

  • We created new ‘instances’ of our slider callback functions (when we call createSliderCallback( i ))
  • Each ‘instance’ contains its own instance variable (audioChannel)

Essentially, we have just achieved object-oriented programming, but using first-class functions and lexical scoping as our building blocks.

There is an old saying that I find helpful in conceptualizing closures.

‘A closure is a poor man’s object… And an object is a poor man’s closure.’

These are really the same things, just slightly different ways of looking at them.

Share on Facebook0Share on Google+0Tweet about this on TwitterShare on LinkedIn0

This entry has 5 replies

  1. spw says:

    Good article. just a little point, I think that

    local audioChannel = which_channel is superfulous.. can just use which_channel in the closure,
    but if I am in error would like to know.

    Keep up the good work.

  2. spw says:

    my bad.
    I notice different behavior when an object ref is passed to the closure rather than a number – perhaps it’s safer to always make a local copy – as you do. sorry

  3. Leo says:

    @SPW and @Ewing. I had the same question. Why do this each time?
    local audioChannel = which_channel

    What’s the advantage. I am confused. Thanks

  4. bobby says:

    Wow man, way to knock this one out of the park; thanks a lot for the concise explanation. I especially liked the final ‘old saying.’

  5. Jason says:

    This just saved what little hair I have left. I’m glad I finally found it…thanks!