Using closures in Lua to avoid global variables for callbacks


The following article is a repost (with a few modifications) of an article I wrote on Ansca Mobile's blog for the Corona SDK. The Ansca blog is busy unlike mine so articles fall off quickly there. So I'm reposting here.

For those who don't know, Corona SDK is an SDK that allows you to write apps for iOS and Android easily using the Lua language with a simple to learn API that some have compared to Flash. Behind the scenes, everything is OpenGL, OpenAL, Cocoa/Obj-C (for iOS) or Java (for Android).

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.

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.)

function makeSliders()
    -- create 4 sliders
    for i=14 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:

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=14 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.

Copyright © PlayControl Software, LLC / Eric Wing