In all my years of scripting, metatables are probably the harder concepts for me to grasp. Some people say that its because I don’t know any OOP, but I disagree because I know a lot of OOP in Java and C++. I have heard the phrase “metatables just describe tables” being thrown around and I am not sure if I heard that correctly. Is this essentially correct?
local table1 = {"john", "bob"}
local metatable = {__index = table1}
setmetatable(table1, metatable)
Does the above code create the following:
table1 = {"john", "bob", {__index = table1}}
Second question: Say I have a module script that creates a gun class.
Gun = {} -- is this the base class?
Gun.__index = Gun
function Gun.new(damage, ammo, reloadTime)
local newGun = {}
setmetatable(newGun, Gun)
newGun.damage = damage
newGun.ammo = ammo
newGun.reloadTime = reloadTime
return newGun
end
function Gun:Fire()
while wait() do
self.Position.X += 1
--do other stuff, not going to list here
end
end
In another script, if I call these two methods, will the following happen?
local Gun = require(ModuleScript.^^^^) -- this is the same as "import" from java?
local new_Gun = Gun.new(14, 23, 53) -- i assume this is instantiating the class?
new_Gun:Fire()
--------------------------------------------------------------------------------------------------------------
--Also is the bottom table correct?
Gun = {__index = Gun, {14, 23, 53}}
I would appreciate if someone cleared metatables up a little bit for me.
No, metatables do not alter the table content. Think of setting metatables as defining operator overloading functions for classes in C++.
Gun.new (constructor) will return a table, containing the properties and namecalling that table (: operator) will access the member functions in the module with “self” being the dereferenced *this pointer (from C++).
require() is kind of like importing a struct (maybe a namespace?) which has properties and functions inside of them.
Objects created won’t be stored in the struct itself so you will only see its __index.
So in terms of java, metatables are kinda like interfaces? and they do not alter the table’s contents. Does that mean the metatable is outside of the table, but somehow describes the table?
I find it helps to think about metatables as a list of events a table can have. Want to know when a new value is added? Use the __newindex metamethod. What about when you try to multiply the table with something else? That’s __mul. There are a lot of these “events” lua-users wiki: Metatable Events.
These functions are not held inside your original table, they are held in the metatable and attached onto it modifying how it behaves. Metatables/metamethods are essentially lua’s implementation of operator overloads.
In your examples local newGun = {} creates an empty table and then the metatable setmetatable(newGun, Gun) is applied, notably with the __index metamethod. What that does is redirect any keys which have a nil value to the Gun table, which conveniently contains all the Gun methods. This way we don’t have to duplicate the functions into every table. Values in the metatable (Gun) can be thought of as static class values here.
Metatables are literally tables used to redefine attributes of a table. You can use setmetatable(targetTable, metatable) to set the targetTable’s metatable. It also returns the targetTable.
So, you’d have a key __index and have a function associated with that key. The __index attribute is whenever the table is indexed and a nil value is found. You get the table it was from, plus the key used as arguments.
An example of using metatables: printing out a table cleanly. __tostring is used whenever the table is converted into a string. So, we can use that attribute to modify what get’s printed out.
local normalTable = {1, 2, 3, 4, 5}
local metatable = {
__tostring = function(tbl) -- We get the source table as arg.
local str = ""
for _, val in next, tbl do
str = str..", "..val
end
return str:sub(2) -- Return the string made
end
}
print(setmetatable(normalTable, metatable)) -- 1, 2, 3, 4, 5
You can get a table’s metatable by using getmetatable(targetTable), if the metatable is not locked, it’ll return the metatable.
Just a super tiny thing that I wanted to point out! In the long run, thinking of them as events might lead into confusion. Thinking of them as behaviors (changes to a table’s set of behaviors) is better.
I usually come across a question that goes along the lines of “why doesn’t lua have an __indexchanged or something that fires when an index is changed”. This is not what metamethods are designed for! Metamethods are to change (override would be a better word) an already existing behavior, and not use them to detect/listen to certain things. They’re not literally events, and the idea of a some sort of __indexchanged is probably inspired from the .Changed event.
In fact, one of the above posts might be an example of such confusion, or maybe just not knowing the grammar metatables follow, this one
I don’t think you got the point. Thinking of them as events leads into the idea that anything you do on a table should have its own metamethods. No.
The __indexchanged example is a perfect one, and I can think of more. Metamethods are overriding default behaviors, not listening for stuff you do with that table.
If you have some python knowledge, dunder methods, which are pretty much lua’s metamethods, are way more complicated than what you’d call an event.
Also, it doesn’t always work both ways, something like __mode for example isn’t an event.
I don’t know much of python yet to learn abound dunder methods (never heard of them until now). But, I see the comparison after a quick read.
Also, I didn’t think of __mode, which doesn’t make sense as an event since it’s a string. So, it does make more sense to think of them as attributes then. I’ll append edit my post about metamethods then about this. Thanks for making it clear.