Magic/Dunder/Meta methods

Greetings! This is my first-ever community tutorial, so please do not hesitate to send feedback.

What you need to know before reading this:

If you’re familiar with Python magic methods, you can stop here; Lua metamethods are similar.
Here’s a list of existing metamethods to get you started: lua-users wiki: Metatable Events
Here’s the documentation for Python if you’re interested:

I haven’t seen any community tutorials on this, and I think it’s very useful in making your modules function in a more user-friendly way for other developers, and adds some useful behaviour too (e.g. printing out a message)!

As an example, let’s say you created a complex numbers module and it works like this:

local ComplexModule = require(ComplexModule)

local a = ComplexModule.New(0, 5) -- 0 is real, 5 is imaginary
local b = ComplexModule.New(2, 3)

-- addition
local result = a:Add(b)
result:PrintResult() -- You can't directly call print(), as it'll give you 
                     -- an unreadable format, so you would probably 
                     -- make a custom method.

Now, what if I told you that this is possible:

local ComplexModule = require(uhh)

local a = ComplexModule.New(0, 5) -- 0 is real, 5 is imaginary
local b = ComplexModule.New(2, 3)

-- look! You can add them together directly, how convenient!
-- and you can also directly use print()!
print(a + b)

This is the magic of lua magic methods.
Many of you should already be familiar with the .__index added to the end of a metatable for
OOP. If you want to know what .__index does, here’s a really good tutorial.

Here’s an example - inside the ComplexModule

local complex_object = {}
complex_object.__index = complex_object


-- constructor
-- If you're unsure what this does, please read up on OOP first!
function complex_object.New(real: number, imaginary: number)
	local self = {}
	
	-- setting properties
	self.Real = real
	self.Imaginary = imaginary
	
	setmetatable(self, complex_object)
	
	return self
end

-- maths operator: +
-- functionality for: complex_a + complex_b
function complex_object.__add(complex_a: complex_object, complex_b: complex_object)
	complex_a.Real += complex_b.Real
	complex_a.Imaginary += complex_b.Imaginary
	return complex_a
end

Here we’re telling what the interpreter does when it tries to add two complex numbers together. I.e.:
complex_a + complex_b.

The .__add() magic method takes in two arguments, both should be of the same type (you can’t add numbers to strings!). The first argument will be the object to the left of the + operator. The second argument will be the object to the right of the operator.
All magic methods can only return 1 result, with a few exceptions.

You can use this to create custom behaviours for addition, subtraction (.__sub), multiplication (.__mul) and more!

Another example:

-- comparison operator: ==
-- functionality for: complex_a == complex_b
function complex_object.__eq(complex_a: complex_object, complex_b: complex_object)
	local real_equiv = complex_a.Real == complex_b.Real
	local imaginary_equiv = complex_a.Imaginary == complex_b.Imaginary
	return real_equiv and imaginary_equiv
end

This time we’re telling the interpreter what to do when == two objects. Naturally, this should return a boolean. The reason you might do this is the default == just wouldn’t work for you. As without our function here, it would compare the entire metastable.

Final example (which is how to get print() working)

function complex_object.__tostring(complex: complex_object)
	return tostring(complex.Real) .. " + " .. tostring(complex.Imaginary) .. "j"
end

Some functions may use magic methods too. E.g. print(). If it can find an existing .__tostring then it will use that. Similarly, you can also use .__name which may be used by tostring() and error messages.

Now you can define behaviour for mathematical operations, comparisons and more!
As a freebie, here’s a module I made which I took all these examples from: https://create.roblox.com/marketplace/asset/15174197720

Happy coding :wave:

1 Like