Meta Tables
02/16/21 08:23 Filed in: Lua
Today, I want to learn about Lua meta tables, what they are and how to use them.
Meta Tables
Lua allows you to attach something known as a meta table to the type of table I learned about last time. The meta table allows you to change how the normal table behaves by using something Lua calls a meta method which have specific names. Meta tables can in turn have their own meta table attached.
There is a limitation in Lua tables. Here is a simple table that has a variable item that has a value of 10.
myTable = {
item = 10
}
You can print the value of item and you can iterate over the table as I learned last time (not shown here).
Although the table only has a single value, you cannot add a number to it.
I can get around this issue by creating a meta table. First, I have to create a table. You can call it anything you want, I'm calling it meta:
meta = { }
And because I'm trying to add a number I can use a meta method with a reserved name: __add
meta.__add = function(left, right)
return left.value + right
end
This meta method is an anonymous function (closure) that takes the value of the table, left.value and adds the right argument to it. I would call the meta function this way:
print(myTable + 1)
But before I can do that, I need to attach (associate) the meta table to myTable. To do this, I use a built-in function called setmetatable.
setmetatable(myTable,meta) — associate meta table to myTable
So, here's a complete example:
And the output:
Cool! But I can't do this:
print(3 + myTable)
Or
print(myTable + myTable)
Because, as written, the __add meta function, on line 7, expects the right parameter to be a simple value.
I can also call the meta function this way:
meta.__add(myTable, 7)
setmetatable associates a meta table to a table or another meta table. There is another meta method, getmetatable, that returns the meta table of a table (or nil if the table has none). getmetatable takes the associated table as an argument.
print(getmetatable(myTable))
In addition to __add, Lua meta tables have an __index meta method. __index can force a table to return a value for a nonexistent key. Using myTable above:
Which prints:
Because __index is called for nonexistent keys, it can be used to return a default value. But, the key (in this case item2) does not exist in the table, even though a value is returned. If I want to ensure the missing key is instantiated instead, I can use the __newindex meta method. You use __newindex like this:
meta = {
__newindex = function(table, key, value)
table[key] = value
end
}
But I don't have to use what is passed in! Here is what I mean:
This requires explanation. Lines 1 and 2 just create two empty tables. Lines 4-12 create a meta table with two meta methods. The __index method will return the value of the key associated with this meta table, c. I'm not using the table parameter in this example, instead hardcoding c. The __newindex is similar, but here I assign the value to the key within the meta table itself. Remember, a meta table is just another table with special methods, so it can have key value pairs as well.
Lines 14 and 15 just associate the meta table with the two empty tables.
Line 17 is more complicated than it looks. Here, I assign a string to the test key within the first empty table. Before assignment, Lua checks the table a and sees it has no key named test. It invokes the __newindex method. This sets the meta table's key. Essentially:
c = { test = "TEST" }
So now tables a and c both have a key test whose value is "TEST".
Line 19 can now print the value.
Line 20 invokes c.__index because table b has no key test. The __init method returns the value stored in the meta table for the key.
Line 21 just prints the value stored in the meta table itself.
When I run it, this is what I get:
That's a pretty obtuse example, but it shows you the power of meta methods. What if I want to bypass the meta methods? For that, Lua provides two other methods: rawget and rawset. These have the following signatures:
rawget(table, key)
rawset(table,key,value)
I use these directly without incorporating them into a metatable (but I could, if I wanted to).
Lua lets me call a table as if it were a function, a functor. Lua calls this a functable, (used as a "function table", not as a verb). To call a table as a method, you use another meta method cleverly named __call. This method takes any number of parameters, but the first needs to be a table. Here is a simple example:
What is interesting is that a table can be its own metatable as shown in line 7. This could cause lots of issues with more complicated meta table logic, but in this example, it's ok.
We call the table message as a function in lines 9 and 10. Here is the output:
Meta tables aren't as powerful as structs in some other languages, but they do allow you to enhance and modify the behavior of Lua tables.