Posted on by

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

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 }
end
end
[/cc]

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 } )
end
end

function makeSliders()
– create 4 sliders
for i=1, 4 do
local new_slider = slider.newSlider{ onEvent = createSliderCallback( i ) }
end
end
[/cc]

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:http://www.lua.org/pil/6.1.html

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.


Posted by . Thanks for reading...

5 Responses to “Using closures in Lua to avoid global variables for callbacks”

  1. spw

    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.

    Reply
  2. spw

    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

    Reply
  3. Leo

    @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

    Reply
  4. bobby

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

    Reply

Leave a Reply

  • (Will Not Be Published)