Troubleshooters.Com and Code Corner Present

Litt's Lua Laboratory:
Callback Functions in Lua
(With Snippets)

Copyright (C) 2011 by Steve Litt



Debug like a Ninja

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