If you don’t come across any instances where you need to use metatables, that’s fine. The only time I use metatable is when I’m dealing with OOP (Object-Oriented Programming) or if I’m extending functionality to something like a function.
Here’s what I mean by the latter:
local function main(self)
print(self.Name)
end
local start = setmetatable({}, {__call=main}) -- set the '__call' metatable to the 'main' function
start.Name = "Roblox" -- add the index to the table
start() -- call the table to execute the 'main' function
Although the times I use anything like the above is quite rare.
For OOP, this post goes into more detail about it:
Most of the time you will use metatables for OOP, but even there it isn’t strictly necessary unless you deal with inheritance which is basically a sin. Other use cases may be for a lazy loading system, for eg calculating the target value of a spring only when you need it, as seen in Quenty’s spring module. Another use case may be for creating a custom datatype such as Vector3 or Quaternion (bad examples don’t try).
OOP is used in cases where you want to create re-usable code or custom Lua objects. It’s mainly present in larger code bases (such as Roact).
The idea of Lua/Luau OOP is to replicate classes and objects from other languages such as C#, Java, and C++.
(Lua/Luau equivalent):
local dog = {}
dog.__index = dog
function dog.new()
return setmetatable({}, dog)
end
function dog:Eat()
end
function dog:Drink()
end
function dog:Sleep()
end
But yes, the post I linked goes into far more detail
I feel like “metatables” makes it seem more complicated than it actually is.
Metatables are tables within tables.
So you could have something like this:
local table_1 = {
["Alphabet"] = {"a", "b", "c"} -- this is another table inside of a table.
}
And you can access “Alphabet” like so:
local alphabet = table_1.Alphabet
The “OOP” most people talk about are functions which are contained inside tables.
local module = {}
function module.Add(a: number, b: number)
return a + b
end
This is actually equivalent to:
local module = {}
module.Add = function(a: number, b: number)
return a + b
end)
Similarly, what most oop modules return is actually a table containing all the methods. The “self” variable is also another table. It’s like a global table but just for that object.
local module = {}
module.__index = module
function module.New(name: string)
self.Name = name
self = setmetatable(self, module) -- this adds all the methods to the self table
return self
end
-- then you can use "self" later on in methods
function module:SayName()
print(self.Name)
end
In a nutshell, the “self” variable is like having a duplication of the modulescript, but has extra information.
It’s routing. Metatables have certain metamethods that allow you to control certain actions within a table.
The __index metamethod is a method that can be used to control the indexing of a table. We can set it to a function and further customize how a table is indexed.
By doing module.__index = module, you are basically creating a route where when the module table is indexed, it will always return the module itself.
You can check out the documentation for more information about metatables.
This code is wrong and will not work. Self is only defined when you initialize a method with “:”.
Here’s a good forum post about how OOP works and what it can be used for:
local self = {} -- forgot to define self here!
self.Name = name
self = setmetatable(self, module) -- this adds all the methods to the self table
return self
After fixing it, I ran it in a local script like so:
local module = require(script.ModuleScript)
local animal = module.New("SkrubDaNub")
animal:SayName()
That is true, but self isn’t really unique as some posts here seem to make me feel. Self is basically just a variable that automatically gets passed to the function if you call it with the colon. You can call the function like module.SayName(module) and it would function identically to using the colon operator instead. Moreover, if you were to define the method without the colon, you can still use it with it, having to define self (maybe even giving it a different name) in the arguments of the function instead.
The purpose of self is to make referencing the object easier.
It’s not mandatory, sure, but it’s highly recommended since it’s much easier than passing the object to the function every time you want to run a method. And if you’re working on a let’s say, public resource, asking the developers to pass the object over and over again just to run a method isn’t going to look good.
A lot of things aren’t mandatory and you are free to do whatever you want, but in my opinion, if you want to be a better programmer, you have to learn how to work fast and simplify things.
I never intended to say that you shouldn’t use self or the colon operator, though. You are correct, it does make it easier. My intent was to clarify what self really was.
Good, but a constructor function would be much better in this case.
local module = {}
local methods = {}
methods.__index = methods -- Routing
-- Setting the methods
function methods:setData(name: string, age: number)
self.Name = name
self.Age = age
end
function methods:printData()
print(self.Name, self.Age)
end
-- Constructor
function module.new()
-- Creating the object
local object = setmetatable({}, methods)
-- Initializing the data
object.Name = ""
object.Age = 0
-- Returning the created object
return object
end
return module