I've been studying metatables on the wiki since day, i understood most of the things unless for some parts; The only thing that i can't really understand is what are they needed for? I heard that they are needed to create classes, if so, what are classes needed for?
Metatables, despite what most people say, are not as complicated as they seem at first glance. People like to describe them as tables within tables, or tables you can attach to other tables, but I personally don't like these descriptions because they don't seem very clear and they certainly don't emphasize the power of metatables. If I had to sum them up in my own words, I'd describe them as such:
Metatables allow you to monitor what is happening to a table. Is it gaining a new entry? Is it invoking tostring
? Is it trying to be called as a function t = {}; t()
? Etc.
Metatables exist to allow the developer to run code when an action is performed on a table.
Some people will also compare metatables to behaving like an event listener for a table, which is not a bad comparison so long as you know the differences between the two. Now, let's move on to how they're used.
There is a built-in function in Lua called setmetatable
-- this function is the only reason metatables exist. You can think of setmetatable as a constructor for creating metatables. However, the function itself only takes two regular tables as it's arguments. Here's an example:
setmetatable({}, {})
The first argument is the table that will keep it's regular behavior, and the second argument is the table that will now behave as the first table's metatable. Here's a more explicit example:
local t = {} -- table local mt = {} -- table that will behave as t's metatable setmetatable(t, mt) -- construct the metatable
Once setmetatable is called, it will return the table it was given as the first argument. In this case, it will return t
. However, t
now has a metatable attached to it. In this case, the metatable is mt
.
This is why people describe metatables as "tables you can attach to other tables," but do you see how much explaining had to be done before we reached that conclusion? Not the best beginner-friendly description in my opinion.
Now that you hopefully have a better understanding of metatables, let's table about metamethods. Metamethods are the fields that go inside of the metatable to control what happens in your program when some action is performed on a table you set a metatable to.
If you like the event listener analogy, you can think of metamethods as events that fire when specific actions are performed to on table.
__index
__newindex
__mode
__call
__mul
__div
__mod
__metatable
__tostring
__len
Don't worry; you don't have to know them all by memory. Most of these metamethods are rarely used anyway. The most frequent metamethods you will probably use are: __index
, __newindex
, __call
, __metatable
, and __tostring
.
The rest you'll only use if you're doing something incredibly specific. But anyway, these are the fields that you will set in your metatable. Let's try __index
, for example:
local t = {} local mt = { __index = function(t, key) return "the index " .. key .. " does not exist" end } setmetatable(t, mt)
The __index metamethod will be invoked when you try to retrieve a value from a table with an index that does not exist in the table. __index can be a table or a function, but I won't go in depth about the description of each metamethod in this answer. You can easily find those anywhere online.
Anyway, in this case, when __index is invoked, it will provide you with the index that you searched t
for. This value is the second parameter of the __index function in the example above (the key
parameter). Let's watch put it to use:
local t = {} local mt = { __index = function(t, key) return "the index " .. key .. " does not exist" end } setmetatable(t, mt) print(t.someKey) --> the index someKey does not exist
We indexed t
for "someKey"
, but since that index does not exist, Lua checks to see if t
has a metatable with an __index metamethod. If so, return whatever __index returns.
This was a very lengthy answer, and it only scratches the surface of how metatables can be used. My goal here was to broaden your understanding of metatables and metamethods more than covering some examples of classes or however else they can be used. If you'd still like an explanation on that, let me know and maybe we can chat through discord.
Hope this helped!
List of all metamethods in Lua: http://lua-users.org/wiki/MetatableEvents
Implementing classes with metatables: http://lua-users.org/wiki/LuaClassesWithMetatable
YouTube tutorial on metatables and metamethod (by me!): https://www.youtube.com/watch?v=aKe6YncHVAo&t=191s
As you probably know by now, any table can be used as the metatable of another table, or even multiple tables at once, which is how you can use some of the mathematical metamethods.
To set a table as the metatable of another table you would use the setmetatable(tbl,metatbl)
function, and to get the metatable of a specific table, you would use the getmetatable(tbl)
function.
Metatables by themselves aren't really that useful, what makes them useful are stored inside them.
What makes a metatable useful are its metamethods, such as __add
or __mul
for mathematical operations. Or the famouse __index
, used in object oriented programming , or OOP.
Arithmetic metamethods constitute of __sub
, __mul
, __div
,__add
and etc. They can be
especially useful for combining sets of numbers. A simple example of the __add
and __mul
would be:
local function add (a) local sum = 0 for _,v in pairs(a) do sum = sum + v end return sum end local meta = { __add = function(a,b) local s1,s2 = add(a),add(b) return s1 + s1 end, __mul = function(a,b) local s1,s2 = add(a),add(b) return s1 * s1 end } local tbl = {1,2,3} local tbl2 = {4,5} setmetatable(tbl,meta) setmetatable(tbl2,meta) print(tbl * tbl2)--36 print(tbl + tbl2)--12
the __index
metamethod is one of the most useful metamethods there is, being especially useful for OOP
The index metamethod can be either a function or a table
Ex function:
local meta = { __index = function(tbl,key) return "attempted to index key: "..key end } local tbl = {1,2,3} setmetatable(tbl,meta) print(tbl["ree"])--attempted to index key: ree
Ex table:
local meta = { __index = {ree = "hey, it exists"} } local tbl = {1,2,3} setmetatable(tbl,meta) print(tbl["ree"],tbl["nope"])--hey it exists, nil
OOP example:
local Account = {balance = 0} function Account:new (o, name) o = o or {Name=name} setmetatable(o, self) self.__index = self return o end function Account:deposit (cash) self.balance = self.balance + cash end function Account:show () print( self.Name, self.balance) end local acc = Account:new(nil,"Builderman") acc:deposit(500) acc:show() -- Builderman, 500
Now as you can see, despite the acc table itself only containing the "Name" key, it also has access to the deposit and show, and new functions due to it having the "Account table" as a metatable
When the index metamethod of a metatable is an actual table, if you tried to print something in the original table that results in a nil value, it will then look in the __index metamethod of the original table's metatable for a value corresponding with the key, if it isn't nil, it will return that value, if it is nil, it will just return nil
Hopefully this helped!
If there is anything i got wrong or missed, please leave a comment