Below is a module script I have for a game I’m working on. The module script is very basic and its used for loading and removing maps from the workspace. When should I use the : notation for the methods within the module script (I.e. function module.loadMap(name) vs function module:loadMap(self, name)
Module Script
local ServerStorage = game:GetService("ServerStorage")
local module = {}
module.CurrentMap = "nil"
module.IsMapLoaded = false
function module.loadMap(name)
local clone = ServerStorage.Maps[name]:Clone()
clone.Parent = game.Workspace
module.CurrentMap = name
module.IsMapLoaded = true
end
function module.removeMap(name)
game.Workspace[name]:Destroy()
module.CurrentMap = "nil"
module.IsMapLoaded = false
end
return module
Yes I was thinking that as well. Not sure why Lua has to make a different notation for properties vs methods though because I much rather prefer other programming languages (such as Java, Python, C++, etc) where properties are accessed as Object.property and methods are Object.method() but if other developers and Roblox use this convention for classes then I guess I will too for readability.
The colon : has a very special place in Lua. It provides the table the called function belongs to as the self argument. self is probably like this in JavaScript.
The two functions in the below example give equivalent results.
-- Module
local module = {}
function module.DoThis(self, arg1)
print(self, arg1) --> module table, "Forest"
end
function module.DoThisWithoutSelf(arg1)
print(self, arg1) --> nil, "Forest"
end
function module:DoThat(arg1)
print(self, arg1) --> module table, "Forest"
end
return module
-- Script
local module = require(script.ModuleScript)
module.DoThis(module, "Forest")
module.DoThisWithoutSelf("Forest")
module:DoThat("Forest")
Colon is especially common in OOP (object oriented programming), because it enables us to send a reference of the object into a function that is part of the object’s class.
Using the colon in examples like yours doesn’t bring any but aesthetic benefits, since self is essentially module. On the contrary, more memory is used because the whole module is pushed into the function as an argument.
“Classes” in OOP are a different story, because the object itself doesn’t necessarily contain the functions developers interact with. Instead, they are contained as part of the class and accessed via metamethods. “If a function is not found in self, look into the class before returning nil or raising an error.”
-- Module
local Map = {}
Map.__index = Map
function Map.new(mapName)
local self = setmetatable({}, Map)
self.MapName = mapName
return self
end
function Map:PrintMapName()
print(self.MapName)
end
return Map
-- Script
local MapClass = require(script.MapClass)
local Map = MapClass.new("Forest") -- .new() is a standard constructor name
Map:PrintMapName() --> "Forest"
If you find the OOP useful, it can prove itself as a great paradigm. As any paradigm, it has certain disadvantages, but the benefits mostly outweight the cons in my use cases, therefore I rely on it regularly.
In your example, the map module could be a class too. The constructed object could contain multiple other objects (focusing on composition over inheritance) and have different functions to add behaviour. The final function could be a destructor to clear the map and disconnect signal connections.
Once you dive a little deeper, you will notice C++ has a lot to do with objects, and the whole Roblox game data model consists of objects (instances).