Litt's Lua Laboratory:
Callback Functions in Lua
(With Snippets)
Copyright (C)
2011 by Steve Litt
Contents
Introduction
Callbacks in Lua
Callbacks in sort()
Introduction
Before presenting this, let's talk a little about callback functions.
A callback function is a function that the calling code installs in
called code so the called code, which is probably very general, can do
specifically what the calling code needs. Did you get that?
Calling code
|
installs
|
callback function
|
in
|
called function
|
Calling code
|
calls
|
called function
|
|
|
Called function
|
runs
|
callback function
|
|
|
Called function
|
returns
|
information
|
back to
|
Calling code
|
This is one of the most powerful programming paradigms because it
enables a general purpose function to do very specific things. This
principle is used in pretty much all generic sort routines -- you pass
the sort routine a function that defines how to tell which element is
bigger than the other, and the sort routine sorts accordingly. Callback
functions form the basis for many types of event driven programming.
Events like on_click are really callback routines.
Perhaps you've done callback routines in other languages. What a syntactic mess! Here's a C example:
Calling function definition
|
|
int binary_op(int a, int b, int (*callback)(int, int))
|
|
Callback function definition
|
|
int plus(int a, int b)
|
|
Passing the callback
|
|
binary_op(5, 3, plus)
|
|
Really? "int binary_op(int a, int b, int (*callback)(int, int))"? Come on C, couldn't you have obfuscated it a little more? Actually, they could have. In C typically every one of those int types would have been preceded by const, but Lua doesn't have const
so I didn't use them. Oh, and most callbacks deal with problem domains
a lot more complex than adding, subtracting and multiplying, so their
declarations are much, much more complex. Anyway, if you ever wonder
why average C application programmers don't use many callback
functions, this is it. Too much syntax.
Perl isn't much better. As I remember, Perl requires a different syntax
depending on whether the callback is part of an object or not. Why "as
I remember?" Because given the syntactic complication, in Perl as well
as C I usually look for other ways to make a generic tool.
By the way, if you want to look at an actual program with the preceding C code, here it is:
#include<stdio.h>
int binary_op(int a, int b, int (*callback)(int, int)){
return callback(a, b);
}
int plus(int a, int b){ return a + b;}
int minus(int a, int b){ return a - b;}
int times(int a, int b){ return a * b;}
int main(int argc, char * argv[]){
printf("Plus : %d\n", binary_op(5, 3, plus));
printf("Minus: %d\n", binary_op(5, 3, minus));
printf("Times: %d\n", binary_op(5, 3, times));
return 0;
}
Ugly, right? Now let's compare the preceding to its Lua counterpart:
#!/usr/bin/lua
local sf = string.format
function binary_op(a, b, callback)
return callback(a, b);
end
function plus(a, b) return a + b end
function minus(a, b) return a - b end
function times(a, b) return a * b end
print(sf("Plus : %d", binary_op(5, 3, plus)));
print(sf("Minus: %d", binary_op(5, 3, minus)));
print(sf("Times: %d", binary_op(5, 3, times)));
OK, let's compare the definition of binary_opp:
C: int binary_op(int a, int b, int (*callback)(int, int))
Lua: function binary_op(a, b, callback)
Yep, I know which language I'd pick if callbacks are a major priority. And they are
a major priority, because you can use them to turn very generically
applicable code very specific. Especially in Lua, where type checking
isn't as thorough.
Callbacks in Lua
Let's review the anatomy of a callback function once more:
Calling code
|
installs
|
callback function
|
in
|
called function
|
Calling code
|
calls
|
called function
|
|
|
Called function
|
runs
|
callback function
|
|
|
Called function
|
returns
|
information
|
back to
|
Calling code
|
Typically the calling code installs the callback function in the called
function by passing the callback into the called function via an
argument. There are other ways using OOP, or using tables.
The callback tells the called function "Here's how I want you to do this particular task."
Let's take a look at the (incredibly contrived) binary_op() example:
#!/usr/bin/lua
local sf = string.format
function binary_op(a, b, callback)
return callback(a, b);
end
function plus(a, b) return a + b end
function minus(a, b) return a - b end
function times(a, b) return a * b end
print(sf("Plus : %d", binary_op(5, 3, plus)));
print(sf("Minus: %d", binary_op(5, 3, minus)));
print(sf("Times: %d", binary_op(5, 3, times)));
All we did was created functions plus(), minus() and times(), which are all obvious. Then we create binary_op(), which uses one of the three, or could also use a function like one of those three, to do its work. The definition of binary_op() shows the callback argument as nothing but a variable name. Simple. The actual calling of binary_op() shows the third argument as nothing but either plus, minus or times. Simple.
Here's the output:
slitt@mydesk:~$ ./testcallback.lua
Plus : 8
Minus: 2
Times: 15
slitt@mydesk:~$
Callbacks in sort()
Lua gives us a function called table.sort()
that takes two arguments, the table to be sorted and a function telling
which of two items is bigger. It sorts the table in place. It only
works on tables whose keys are integers! Fortunately it's easy to work
with any table in order to implement sorting. Most typical is a two
dimensional table simulating a database table. Perhaps something like
this:
people = {
{fname = "Zeke", lname = "Adams"},
{fname = "Aaron", lname = "Zyzmanski"},
{fname = "Eddie", lname = "Tanner"},
{fname = "Bill", lname = "Tanner"},
{fname = "Fred", lname = "Tanner"},
{fname = "Eddie", lname = "Edwards"}
}
You can sort by first name A-Z, first name Z-A, last name A-Z, first
name Z-A. You can also do a double sort in case anyone has the same
first or last name, as otherwise it won't sort completely right on
items with the same primary sort name. For instance, if you sort by
last name, the three Tanners aren't guaranteed to come in at first name
order unless you write your function that way.
The following is a program that first prints the unsorted people array, then sorts by first name, then by last name, and then by last and first:
#!/usr/bin/lua
sf = string.format
people = {
{fname = "Zeke", lname = "Adams"},
{fname = "Aaron", lname = "Zyzmanski"},
{fname = "Eddie", lname = "Tanner"},
{fname = "Bill", lname = "Tanner"},
{fname = "Fred", lname = "Tanner"},
{fname = "Eddie", lname = "Edwards"}
}
for k, v in pairs(people) do
print(sf("Fname=%s, lname=%s", v.fname, v.lname))
end
function byfirst(a,b) return(a.fname < b.fname) end
function bylast(a,b) return(a.lname < b.lname) end
function bylast_then_first(a,b)
if a.lname > b.lname then
return false
elseif a.lname < b.lname then
return true
else
return a.fname < b.fname
end
end
table.sort(people, byfirst)
print("===============")
for k, v in pairs(people) do
print(sf("Fname=%s, lname=%s", v.fname, v.lname))
end
table.sort(people, bylast)
print("===============")
for k, v in pairs(people) do
print(sf("Fname=%s, lname=%s", v.fname, v.lname))
end
table.sort(people, bylast_then_first)
print("===============")
for k, v in pairs(people) do
print(sf("Fname=%s, lname=%s", v.fname, v.lname))
end
The following code produces the expected output:
slitt@mydesk:~$ ./test.lua
Fname=Zeke, lname=Adams
Fname=Aaron, lname=Zyzmanski
Fname=Eddie, lname=Tanner
Fname=Bill, lname=Tanner
Fname=Fred, lname=Tanner
Fname=Eddie, lname=Edwards
===============
Fname=Aaron, lname=Zyzmanski
Fname=Bill, lname=Tanner
Fname=Eddie, lname=Edwards
Fname=Eddie, lname=Tanner
Fname=Fred, lname=Tanner
Fname=Zeke, lname=Adams
===============
Fname=Zeke, lname=Adams
Fname=Eddie, lname=Edwards
Fname=Fred, lname=Tanner
Fname=Bill, lname=Tanner
Fname=Eddie, lname=Tanner
Fname=Aaron, lname=Zyzmanski
===============
Fname=Zeke, lname=Adams
Fname=Eddie, lname=Edwards
Fname=Bill, lname=Tanner
Fname=Eddie, lname=Tanner
Fname=Fred, lname=Tanner
Fname=Aaron, lname=Zyzmanski
slitt@mydesk:~$
This is a fantastic demonstration of the power of callback functions. The table.sort()
function was written to cover a sort of absolutely any table with
integer keys, and you as the application programmer define the exact
sort using a callback routine.
We've just scratched the surface of callback routines. Enough that you know what they are. Because in the Lua Closures page you'll see an outstanding example of callbacks with the "Practical Line Skipper Iterator" article.
[ Troubleshooters.com|
Code Corner | Email Steve Litt
]
Copyright
(C) 2011 by Steve Litt --Legal