User:Anaminus/metatables nutshell
Metatables
Here's a not so small bit on what metatables are and how they work.
Definition
The prefix meta- means that something is about is own category. Meta'something' basically means 'something' about 'something'. You can say that metatables are "tables about tables", which is very true.
How do you set a metatable to a table?
There's a function called 'setmetatable'. The first argument is the table you want to set a metatable to, and the second argument is the actual metatable. Here's an example:
table = {} metatable = {} setmetatable(table,metatable)
Setmetatable also returns the metatable, for convenience.
Now the table has a metatable.
But how can you look up this metatable?
There's no value in the table that references it. You can tell this because this example will not print anything:
for i,v in pairs(table) do print(i,v) -- 'i' is the table key and 'v' is the key's value end --> (nothing)
That's why there's a function called 'getmetatable'. We can use this function to get the metatable associated with the table. REMEMBER: this function returns nil if a table does not have a metatable set! Let's build off of the other examples:
print(metatable) --> table: 065CB118 print(getmetatable(table)) --> table: 065CB118
Notice how the the two are the same. That indicates that 'table' does indeed have 'metatable' set as it's metatable. Also note that use can still use 'metatable' as a reference to the metatable.
Now, how can metatables be useful?
There's definitely more uses, but I will explain those later. I just wanted to point out that metatables can be used to store information in a table without making a direct value in the table. Here's an example:
t = {a=1, b=2, c=3} mt = {d=4, e=5, f=6} setmetatable(t,mt)
And then...
for i,v in pairs(t) do print(i,v) end --> a 1 b 2 c 3
Notice how the data in the metatable was not mentioned. This can be useful for storing hidden data that you don't want people to access. There's a bit on how to prevent access to a metatable, which I will also explain later.
Now, since a metatable is also simply a table, we can use the same methods to get it's contents:
mt = getmetatable(t) for i,v in pairs(mt) do print(i,v) end --> d 4 e 5 f 6
And also, since metatables are tables, they can also have their own metatables! Here's a rather silly example:
print(setmetatable({},setmetatable({},setmetatable({},setmetatable({},{}))))) --> table: 065C5438
Metamethods
For this next part, keep in mind that metatables are simply tables, so they are not any more complex to build than a normal table.
This is what makes metatables so incredibly useful. They have something called metamethods. Basically, these are events. These events are fired when a table is manipulated in a certain way.
What's so special about these metamethods is that you are able to call a function when one of these events is fired (much like roblox events, eh?).
But how do you access these events?
Simple! You set a certain value in the metatable. There is one called '__index'. It is probably the most common one used, so I'll use it in the examples. There are many more special events, but I'll only explain a few. What is supposed to happen to get __index to fire? Index fires when something in the associated table is looked up:
t = table[1] -- looked up print(table[key]) -- also looked up table[b] = table[a] -- 'a' is looked up.
Going back to events in general, events act like values in the metatable. You can set one just like you would set a value in any table:
mt = {__index = print} -- or <pre>mt["__index"] = print -- or <pre>mt.__index = print
But what makes an event different from any other value in a metatable?
No, it's not the fact that they have a double underscore before their name, it's simply their name. It's broken down like this: '__index' is an event, and 'blahblahblah' is not. '__add' is an event, and 'add' is not. You pretty much have to learn which names are events. Any other value is not an event. No, you cannot make you're own events by adding '__' to the name; you cannot add your own events at all, anyway. You just have to work with what is there.
Anyway, what's this calling a function all about?
You can set a function to an event by simply making the function the event's value:
mt.__index = function(t,k) return "No peeking!" end
The skeleton of it is something like this: metatable[event] = function</syntaxhighlight>
Most of the events will pass arguments to the function when it is fired. In the example, the values 't', and 'k' are the arguments. The index event pass these arguments: the associated table, and the key looked up.
The function in the example returns the string "No peeking!". So, instead of getting the looked up value, you would get "No peeking!".
Some Events
Now I'll explain a few of the events.
__index
As I've mentioned, this event fires when something in the table is looked up.
print(table[key]) --> fires __index
The arguments it passes are the table, and the key looked up.
__newindex
This fires when a key that doesn't exist in the table is looked up.
table[key] = value -- fires __newindex
table.a = 1 -- also fires __newindex
table.b = table.a -- fires both __newindex and __index
The arguments it passes are the table, the new key, and the new value.
__add
This fires when the table is used with the '+' operation.
print(table + 3) -- fires __add
The arguments passed are the table and the value the table was "added" with.
You can begin to see how useful these events can be. Normally you couldn't add a table and a number, because a table is not a number. But, if you set a function to the add event that returned a number, instead, it would avoid errors. If you tried this example, you might have gotten something like this:
attempt to perform arithmetic on global `table' (a table value)
Lets see what happens when we set the __add value in the metatable:
setmetatable(table,{__add = function(t,n) return 7 end})
print(table + 3)
-->
10
__metatable
This fires when the metatable of the table is accessed.
setmetatable(t,{}) -- fires __metatable
getmetatable(t) -- fires __metatable
You can use this if you want to protect the content of the metatable from being accessed or changed. All you have to do is set the value.
setmetatable(t,{__metatable = "No looking!"})
print(getmetatable(t)) --> No looking!
setmetatable(t) --> cannot change a protected metatable
Here's a little trick if you wanted to make the metatable read-only:
setmetatable(t,{__metatable = getmetatable(t) end})
print(getmetatable(t)) --> table: 065CB1B8
setmetatable(t,{}) --> cannot change a protected metatable
External Links