Skip to content

96. Meta-what? (metatables explained)

July 21, 2013

Metatables. Sounds a little scary. The code looks strange.
http://instagram.com/p/cAvklohHbK/
So, can we understand these weird beasts (and should we care if we don’t understand them?).

You can do various things with tables, like insert and remove items, and convert a table to a string. You may have experimented to see if you can do things like adding tables together, or test whether two tables are the same, and found that you can’t.

Metatables let you define functions like add and compare, so your tables can do these things, and so table1 + table2 will do something useful.

Let’s start with just adding tables. Assume we want to add two tables A={1,2,3} and B={10,20,33}, to get C={11,22,33}. We could just write a function to do it, like this

function AddTables(a,b)
    --return nil if different sizes
    if #a~=#b then return nil end
    --put total in temporary table
    local t={}
    for i=1,#a do
        t[i]=a[i]+b[i]
    end 
    return t
end

and then we’d write C = AddTables(A,B)

Let’s do the same thing with a metatable.

Now just imagine you were designing the Lua language, and you thought

“tables are really important, but most of the ‘operators’ we use for variables, such as + – * / ^ etc, don’t work with tables. Wouldn’t it be useful if developers could define their own functions for these operators, so you could write C = A + B?”

So they invented metatables. You can understand them more easily if you think about how you would do this yourself, ie how you would allow developers to define what happens when you add (or subtract, or…) tables together.

Let’s just use the addition example above to start with. First, the developer needs to create a function that adds two tables – we’ve already done that above. Then, they need to somehow assign it to the plus operator for tables, so when you say “table1 + table2”, Lua will automatically use our function above to add the tables.

So how should we tell Lua that when we write C = A + B, we want to use our special AddTables function? I need to remember there are perhaps 20 different operators I might want to define, so I need to keep things organised.

The way it’s done in Lua is that you put all these things in their own table, and you use keywords to tell Lua which operator you want to provide code for. We want to use ‘+’, which has the keyword __add (that’s a double underscore).

Here is the complete code for the metatable version (excluding the AddTables function). I’ve explained it underneath.

    A={1,2,3}
    B={10,20,30}  
    m={} --table to hold operator definitions
    m.__add=AddTables --function to run for addition
    setmetatable(A,m) --assign m to A
    C=A+B  --do addition
    for k,v in pairs(C) do --test it worked
        print(k,v)  --> 11,22,33
    end

Having defined A and B, we define a table m to hold our special instructions to Lua. Then we assign the function we want to run when we add, to the property __add (which is Lua’s keyword for the + operator). Why two underscores? I guess they wanted to make sure this didn’t get mixed up with any other variable names.

Then we use setmetatable to attach our instruction table m, to A.

So when we write C = A + B, Lua checks whether either A or B has a “metatable” assigned. It sees that A does, a table called m. Since we are adding, Lua looks in m for a property __add. If it’s not there, there will be an error because you can’t add tables normally. If it finds __add, then Lua runs the function named in there, ie AddTables, which will add A and B.

So let’s just go over that one more time.

  • We want to add two tables using a plus sign, like we do for two numbers.
  • We write a function to do the work
  • we set up a separate table
  • give it a property __add, equal to our function name
  • (we can add more properties for other operatores like x and – if we want)
  • we use setmetatable to link this table to one of the tables being added
  • .
    Why is it called a metatable? Because it contains instructions rather than data.

    You might want to define ‘+’ differently. For example, suppose you are developing an RPG game with an inventory table of named items containing everything you pick up. Each time you open a trunk, you want to update your inventory, increasing the count of anything you have already, and adding any new items. So you might write a function like this

    function AddItems(a,b)
        for k,v in pairs(b) do
            --add item if not already there
            if a[k]==nil then a[k]=0 end 
            a[k]=a[k]+b[k] --update item count
        end
        return a
    end
    

    Then you would use exactly the same code as above, to create a metatable and attach it to your inventory table.

    What tags are available to you?
    __add: Addition (+)
    __sub: Subtraction (-)
    __mul: Multiplication (*)
    __div: Division (/)
    __mod: Modulos (%)
    __unm: Unary -, used for negation on numbers
    __concat: Concatenation (..)
    __eq: Equality (==)
    __lt: Less than (<)
    __le: Less than or equal to (<=)
    (These come from a nice page explaining metatables, here).

    You don’t have to use them for the purpose shown above, eg you could use __sub to add instead of subtract, but that would just be confusing.

    Yes, but what is this good for?

    To be honest, nothing above excites me too much.

    I prefer to write C = AddTables(A,B) because it is clearer to me what is happening, than C = A + B. I suspect metatables are most useful for large projects where you might do a lot of table work, and then it would be useful to make your code more compact and easy to read.

    Using metatables to manage indexes

    Apparently, a common use of metatables is to handle situations where a table lookup returns nothing (ie not found). If this happens, Lua checks the metatable (if there is one, of course) to see if the property __index has been defined. If it has, then Lua will run whatever function has been attached to __index. This can be used to trap errors, or provide an alternative table to look up instead. I haven’t given an example here because I couldn’t find one that I thought was generally useful.

    Using metatables to enhance existing libraries

    A forum poster wanted to add extra functions to mesh, eg AddTriangle. He tried this (treating mesh as a class to which you can add functions)

    function mesh:addTri(x1,y1,x2,y2,x3,y3)
             -- Put code to add a triangle here
        end
    

    and it didn’t work. The reason is that mesh is not a class or a table, but ‘userdata’ (ie stuff you can’t mess with). However, there is a way to add functions to mesh, like this.

    --get metatable of mesh
    local mt = getmetatable(mesh())
    mt.addTri=AddTriangle --our new function
    
    --where you've defined
    function AddTriangle(x1,y1,x2,y2,x3,y3)
        -- Put code to add a triangle here
    end
    
    --then you can write
    m=mesh()
    m:addTri(100,200, etc)
    

    Of course, I had to try adding additional functions to the metatables of ordinary tables in the same way, and it didn’t seem to work except for the special keywords. Oh well.

    Summary

    If you are a code wizard, you may find good uses for metatables, but I think there isn’t much need for us ordinary mortals to use them. But at least we have some idea of what they are for.

    Advertisement

    From → Programming

    4 Comments
    1. briarfox permalink

      Thanks Ignatz, metatables actually make sense to me now 🙂

    2. Donald permalink

      I like the image. did you make the the metal beast in the picture?

    Leave a Reply

    Fill in your details below or click an icon to log in:

    WordPress.com Logo

    You are commenting using your WordPress.com account. Log Out /  Change )

    Twitter picture

    You are commenting using your Twitter account. Log Out /  Change )

    Facebook photo

    You are commenting using your Facebook account. Log Out /  Change )

    Connecting to %s

    %d bloggers like this: