I’ve been reading/watching a few tutorials on how metatables work and I’m still wondering what the purpose of them is for?

local larry = {}
larry.skills = {speed = 20, strength = 200}
larry.metatable = {__index = larry.skills}
function larry:new()
local a = {}
setmetatable(a,larry.metatable)
return a
end
local larry = larry:new()
print(larry.strength)

output prints 200

so from my understanding, metatables are basically tables being attached to other tables, so it’s easier to call values inside of a table that doesn’t have a value. But isn’t this a bit counterintuitive? why not just do it this way?:

local larry = {
skills = {
speed = 20;
strength = 200;
}
}
print(larry.skills.strength)

output prints 200

This the same outcome, but just more simpler. I keep hearing that metatables are the way to go on keeping your code clean and organized, but honestly it looks like a mess. Is there any reason why metatables work better?

local larry = { }
larry.__index = larry
function larry.new()
local new_larry = { }
function new_larry:eat()
-- eat
end
function new_larry:clean_plate()
-- clean plate
end
setmetatable(new_larry, larry)
end

Now imagine you have hundreds of instances of larries.

You would also have hundreds of copies of functions. This is terrible for memory! So what is instead used is __index, so that you only get the value when it doesn’t exist so it isn’t a part of every table.

What I like to use metatables to share functions between scripts for easy editing, also making adaptive modules that can pretty much handle everything.

You can also index self, which allows you to easily store data and index it.

My most recent use of a metatable was in a wrapper module for the math library I’ve been working on.

I set the built-in math table as the metatable’s index so that when a user tries to access a function or constant that isn’t in the wrapper, it would search for it in the math library.

local module = {
e = math.exp(1)
}
function module.isReal(n)
return ((typeof(n) == "number") and (n == n) and (math.abs(n) ~= math.huge))
end
function module.isInt(n)
return module.isReal(n) and (math.floor(n) == n)
end
function module.round(x, degree, base)
assert(module.isReal(x), "Input must be a real number.")
if (degree ~= nil) then
assert(module.isInt(degree), "Degree must be an integer.")
else
degree = 0
end
if (base ~= nil) then
assert(module.isInt(base), "Base must be an integer.")
assert((base > 0), "Base must be greater than zero.")
else
base = 10
end
local n = base ^ degree
return math.floor(x * n + 0.5) / n
end
function module.blahblahblah()
-- etc.
end
return setmetatable(module, {__index = math})

local wrapper = require(mathmodule)
-- 'round' is a function in the module, 'pi' is in the math table.
print(wrapper.round(wrapper.pi, 2)) -- 3.14