Custom Metamethods

After a preliminary round of research, I’ve learnt that metatables are tables that have custom behaviors when other functions are called on them (ie. metamethods). In my game, I’m using @GollyGreg’s module script filled with metatables, which somehow makes it so that I can call methods that aren’t the metamethods Roblox listed in their guide on metatables, leading me to assume that I can create custom metamethods. However, that aforementioned module script is a proving to be quite tricky for me to replicate. Simply put, how do I write and use custom metamethods like GollyGreg does in that module script?

I do not see these custom metamethods you speak of in @GollyGreg’s code. I see only two proper metatables with a single standard metamethod for OOP emulation. Can you elaborate?

In the Renderer script, there is a table called Renderer and a bunch of functions below it that are styled like methods (eg. line 53 with function Renderer:withRenderImpl(implementationFunc)). I assumed that Renderer is a metatable because on line 46, there is a setmetatable global with Renderer as the metatable argument. Soon after, state (ie. the table getting turned into a metatable) is returned, which ultimately ends up being the return value of the createRenderer script, which I then use as if it were a normal Roblox class (eg. renderer:AddToStack(someHighlight)). Are these not metamethods? What are they called? How do I replicate them?

Renderer is a metatable, but what you’re referring to are not metamethods. A metatable is no different from a normal table, when when assigned as the metatable to another, Lua(u) will read that table for specific keys. Those keys can be found here. These keys modify how Lua(u) interprets interactions with the table the metatable was attached to. For example, you can use them to redefine what it means to subtract two tables.

What you’re looking for are just plain methods. This is the term given to functions of a class. To create one, you need only use the following syntax:

function Class:Method(--[[parameters]])
    -- Function body
end

This enables the implicit “self” parameter, which is a fundamental component of emulating OOP in Lua(u). To learn more about how this works, read this reply to another post.

The driving metamethod in OOP emulation is the __index metamethod, which can be assigned to either a function or another table. This metamethod is activated whenever Lua(u) detects an index operation on a table where no value is associated with the index. The following happens:

  1. If __index is assigned to a function, the function is called with the accessed table and the key.
  2. If __index is assigned to a table, Lua(u) will propagate the index operation onto that table and return the result.

Developers use the latter behaviour to connect the methods of the class with an instance of the class, which by itself is a table solely comprised of properties.

instance:Method(...)

Attempting the above results in the following operation:

instance.Method(instance, ...)

This starts an index operation on instance for the key “Method”, but since instance lacks this key, the index operation propagates to the table stored in the attached metatable’s __index key, which is the class. This results in the following operation:

class.Method(instance, ...)

Since the method does belong to the class, the function is successfully located and executed with the particular instance of the class and any related arguments.

Altogether:

local Class = {}
Class.__index = Class


function Class.new(stringA, stringB)
    local self = setmetatable({}, Class)

    self.StringA = stringA
    self.StringB = stringB

    return self
end

function Class:Method()
    print(self.StringA .. self.StringB)
end


local instance = Class.new("Hello", ", world!")
instance:Method() --> Hello, world!
3 Likes

One last question. From that post on explaining self, it gives me the impression of being something akin to this from C++ (ie. a way to refer to the “class” that we make with module scripts). Therefore, in my head, self in Class.new would be referring to Class itself. However, you’ve put local before it, which gives me the impression that it will shadow self with the metatable. So, did you intend to shadow self, or is there some other reason you’re using “self” as the name of the metatable to return?

You’re correct! self is intended to act as a reference to the instance of the class. In C++, and other languages, it is called this. However, unlike these languages, when it comes to the constructor, the implicit parameter is no longer available. I explain why that is in the same reply I mentioned earlier. Because the parameter is no longer available, its assignment in the constructor is the first instance of that variable, meaning no shadowing is occurring. It is conventional to name the variable “self” as it continues to hold up the notion that “self” refers to the instance of the class, which we’re creating in the constructor

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.