Anonymous Functions and Closures

This tutorial discusses the basics behind anonymous functions and closures in Lua. To begin, let's inspect how a typical Lua function looks:

local function myFunction( parameters )
    -- Function content
end

Under the hood, this creates a variable called myFunction that is scoped locally to this code block and is stored where the code block exists in memory. For instance, if you execute print( myFunction ), the console will print something like 0x4a8cd834. Basically, this is the location in memory where Lua will jump when the function is called.

Lua also permits you to define a function like this:

local myFunction = function( parameters )
    -- Function content
end

In this notation, it's more clear that you're assigning a variable to a code block, or more specifically the memory address of that code block. In the end, both notations accomplish the same thing, but the second notation allows some additional flexibility. When written in this form, you are effectively taking an unnamed block of code and giving it a name.

Anonymous Functions

As yet another option, Lua permits you to write a function without a name. This is called an anonymous function. To understand the difference between regular functions and anonymous functions, let's look at some examples:

local function myFunction( parameters )
    -- Function content
end

timer.performWithDelay( 1000, myFunction, 10 )

In this case, the timer.performWithDelay() function is given a time interval of 1000 milliseconds, a function to execute, and an integer count to repeat. Effectively, myFunction() will fire 10 times at an interval of 1000 milliseconds, but what's internally happening is that Lua is passing the memory address of the myFunction() function to the timer.performWithDelay() function.

This means that you could also write either of these commands:

timer.performWithDelay( 1000,
    function()
        print( "Hello World" )
    end,
10 )
timer.performWithDelay( 1000, function() print( "Hello World" ); end, 10 )

In the second example, notice the semicolon (;) following the print( "Hello World" ) command. If you write an anonymous function with multiple commands on one line, each command must be followed by a semicolon, indicating to Lua where each new command begins.

In both cases, instead of using a variable which references a function's memory address, you simply create an unnamed anonymous function that will execute when the timer fires.

When to Use Them

Anonymous functions are most useful when you need to code an onComplete or callback listener function, for example in timer.performWithDelay(), transition.to(), or audio.play() — but how often should you use them? From the perspective of code readability and sustainability, perhaps not very often, but if the function will only be used in that specific scope of your code, and it's relatively short, anonymous functions are a perfectly valid coding technique.

Note that there are times when you can not use anonymous functions. One specific instance is when you assign functions to event listeners. Because the event listener system in Solar2D is based around storing the address of functions that are specified to handle events, the address that you pass in when removing an event listener via object:removeEventListener() must match the address that was passed in to object:addEventListener().

Lua Closures

Lua closures are another useful technique. Where they truly shine is in passing parameters to function handlers that are not looking for extra parameters. To illustrate this, let's look at a transition.to() example:

local function myFunction( parameters )
    -- Function content
end

transition.to( playerObject, { time=1000, alpha=0, onComplete=myFunction } )

In this example, myFunction() will be passed a single parameter, target, which is the object that is transitioning. But let's assume that the display object is a member of an array and you need to know more about the object itself in the function that gets called. This can be accomplished via a Lua closure:

local function myFunction( parameters )
    -- Function content
end

local balls = {}
balls[1].ball = display.newImageRect( "greenball.png", 64, 64 )
balls[1].color = "green"
balls[2].ball = display.newImageRect( "redball.png", 64, 64 )
balls[2].color = "red"

for i = 1,#balls do
    transition.to( balls[i].ball,
        { time=250, x=200,
            onComplete=function( target )
                myFunction( target, balls[i].color )
            end
        }
    )
end

In this example, instead of calling myFunction() as part of the callback, you call an anonymous function instead. That anonymous function takes the received target parameter that transition.to() provides and, inside it, you call your named function, passing along the target object reference and the color property of the ball.

Closures are more than just anonymous functions. When you have a function inside of a function, the child function has access to all of its parent's local objects, or upvalues. Normally, when a function ends, any locally-scoped variables go out of scope, so when you call the function again, you begin anew. Consider this example:

local function counter()

    local i = 0
    while i < 10 do
        i = i + 1
    end
    return i
end

print( counter() )

In this case, when you call the counter() function, it will print the value of 10 because when the function's scope ends, so do its local variables.

Now let's modify the function to actually return a function that does the counting:

local function counter()

    local i = 0
    return function ()
        i = i + 1
        return i
    end
end

local counter1 = counter()

print( counter1() )  --> 1
print( counter1() )  --> 2

Following line 10, we have a new function in the counter1 variable, and when we run it multiple times, Lua realizes that the anonymous function may still get used and that it should hold on to the current value of i in its current scope. This is what a Lua closure can do for you.

To expand on this concept, if you were to create another counter, the following results will occur:

local counter2 = counter()

print( counter1() )  --> 3
print( counter2() )  --> 1
print( counter1() )  --> 4
print( counter2() )  --> 2

Essentially, each version of the anonymous function holds its own i and keeps it scoped within itself. So, when you create a second counter, i resets to 0 for that instance.

Conclusion

As you can see, anonymous functions and closures provide a powerful way to keep track of variable instances on a per-function basis. In the context of Solar2D, this allows you to pass parameters to functions in which you normally don't control the passed-in content.