Lua: Methods

From Mario Fan Games Galaxy Wiki
Revision as of 18:25, 18 May 2009 by Xgoff (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
 Standardwikimessagebox.png This page should be syntax highlighted

Code on this page would be more readable and may wrap correctly if it were surrounded by <source> tags using the enclose="div" attribute.

Lua
Lua.gif
Basics
Intermediate
Advanced
XLua
Add to this template
 Standardwikimessagebox.png This article assumes the use of Lua 5.1.

Information may not be accurate or may need revision if you are using a different version.

Methods are functions that are used by an object. 'Object' doesn't necessarily mean a game object, but instead some data structure that contains a set of variables and functions (the methods). Of course, the data structures we will be using are tables (usually).

Lua's methods have a special syntax; you don't need to use this syntax, but it's cleaner and easier than the alternative. Called colon syntax, it of course uses a colon instead of a dot:

-- regular function
table.function()

-- method
table:method()

Basically, by using the colon, you insert the data structure immediately left of the colon as a hidden first parameter. Therefore, these two calls are equivalent:

table.method(table, other_param)
table:method(other_param)
 

When you're writing the source code, however, that first parameter will generally be something else. It's just a regular variable, so it doesn't necessarily need to have the same name as the data structure calling the method (you should avoid this, in fact). You'll often see this variable named 'self', but there's nothing forcing you to call it that. However, this tutorial will use that as it's commonplace.

Table Methods

In order to use methods for any given table, we need to give it a metatable. In fact, we will make the table it's own metatable. This is as simple as taking the function from the Metatables tutorial and changing it a little. Because we don't want the methods to actually "pollute" this table, we will have them stored in a separate table called methods. So our code should start out like this:

methods = { }

function newTable(...)
    local t = ...
    setmetatable(t, t)
    t.__index = methods
    return t
end

You'll notice how setmetatable has two 't' parameters: we have set the table to use itself as its metatable. You should also notice a new line, t.__index = methods. This is the index metamethod, a table or function which is called when a lookup fails (because there is no entry there). Because a method lookup in one of our tables will always fail (we aren't storing the methods there, remember), __index tells Lua to look in the methods table for the requested index. As long as we give it a method that actually exists, it will find it just fine.

Now we need to write a method for the methods table. A useful one will return the length of the entire table, including non-numeric keys, which are normally ignored by the length operator. So our methods table will look like:

methods = {
    length = function (self)
        local l = 0
        for k in pairs(self) do
            if string.sub(k, 1, 2) ~= "__" then
                l = l + 1
            end
        end
        return l
    end,
}

This is a similar table iteration as the __add metamethod from the Metatables tutorial. However, instead of adding the values at two tables' indexes, we're just incrementing a variable to use as our length. Note how we are using string.sub to exclude key names starting with '__'; we don't want metamethods to be counted in the length! We're actually done now, so we should try testing it:

methods = {
    length = function (self)
        local l = 0
        for k in pairs(self) do
            if string.sub(k, 1, 2) ~= "__" then
                l = l + 1
            end
        end
        return l
    end,
}

newTable = function (...)
    local t = ...
    setmetatable(t, t)
    t.__index = methods
    return t
end

test = newTable {1, 2, 3, a = 4, b = 5, c = 6}

print(test:length()) -- prints 6
print(test.length(test)) -- prints 6 (longhand for above)
print(#test) -- prints 3!
print(newTable {1,2,3, four = 4}:length()) -- believe it or not, this works too (prints 4)

Methods are a nice way to make your code not only cleaner, but also more modular. Instead of having to repeat the same code over and over for multiple tables, you can just __index them to the same methods table and they can all take advantage of methods.