As a Roblox developer, it is currently too hard to use object oriented programming without having to worry about low level stuff (especially with Luau type checking) and make my code look clean.
I know, true OOP isn’t coming to Lua or Roblox anytime soon but good feature would be syntactical sugar for classes which would use metatables under the hood.
Why?
Read this post which explains all of the nightmares you’ll get when you try to use OOP with Luau type checking such as painful types like {__meta: {GetMember: ({-member: free6192-1-0-}) -> free6192-1-0, __index: <CYCLE>, new: (string) -> ({__meta: {GetMember: ({- member: free6190-2-1 -}) -> free6190-2-1, __index: <CYCLE>, new: (string) -> (<CYCLE>, free6193-1-0) }, member: string}, free6193-1-0)}, member: string}.
As @Shedletsky and @Elttob mentioned in that topic, classes made using my proposed syntactical sugar improve readability. I would personally prefer syntactical sugar for classes over the current way of writing classes.
As @davness said in that topic, currently you have to deal with low-level stuff like setting __index (also types when Luau comes out). Developers should focus on the class, it’s methods and properties instead of the low-level stuff. Those low-level things could as well be handled by Roblox under the hood.
How?
In that topic, I proposed a simple, clean way of writing classes. I came up with ideas and here they are:
class Player
username = "Username" -- Class property
hair = "None"
constructor(playerUsername) -- Class constructor
self.username = playerUsername
end
function ShowUsername() -- Class method
print(self.username)
end
end
class BaconhairPlayer << Player -- Inheritance
static hair = "Pal Hair" -- No override keyword needed & static keyword can be used for making properties or methods static
static username = "UseStarCode_BACON"
end
print(BaconhairPlayer.hair)
print(BaconhairPlayer.username)
local devKittenzPlayer = new Player("Dev_Kittenz")
devKittenzPlayer:ShowUsername()
print(devKittenzPlayer.hair)
With Luau, the Player class would look something like this:
class Player
username = "Username": string -- Class property
hair = "None": string
constructor(playerUsername) -- Class constructor
self.username = playerUsername
end
function ShowUsername(): nil -- Class method
print(self.username)
end
end
-- No "type Player = typeof(new Player())" is required, Roblox automatically creates one
Those new keywords could be introduced as context-sensitive ones so they would be fully backwards-compatible.
If Roblox is able to address this issue, it would improve my (and most likely many other devs) development experience because then we wouldn’t have to worry about low-level stuff when writing classes, types would be handled by Roblox under the hood and our code could look cleaner and easier to read.
No offense, but you can avoid this entire problem if you didn’t use OOP. Since there is no native support for OOP in Lua, ofc it’s gonna be a pain to implement.
OOP is a popular and widely used tool in many top Roblox games today, so it would be dangerously ignorant to dismiss it entirely, no matter what you think of it personally.
Also, a suggestion for the syntax; in line with the syntax for typed Luau, perhaps the inheritance ‘operator’ could be a left arrow <- or colon :?
The inheritance symbol can only appear after a class statement, and so there would be no ambiguity as those maths operators don’t ever appear at the start of a Lua statement, and therefore can never be directly after the start of a class block.
Consider this snippet:
class Foo <-
In this case, the only token that could follow is an identifier.
Now, consider this:
class Foo
The only tokens that could follow here are an identifier, a comment, a keyword or parentheses. No relational infix operators can ever appear immediately after the identifier token representing the class name.
I’m assuming you didn’t notice how, in my first reply, I used the term ‘operator’ in quotes.
The inheritance symbol would in reality not be represented as one token, most likely. It’s just as sufficient to consider it as two separate symbol tokens without any whitespace tokens between them, and completely avoids that issue.
Anyway, getting back to the main topic: I think that the benefit and standardisation of a widespread and hard to type check coding pattern is well worth the engineering cost to implement it. The addition of the continue pseudo-keyword is pretty solid proof that Luau is capable of handling those kinds of contextual keywords.
Outside of detecting the class pseudo-keyword, there’s actually not a whole lot of contextual stuff going on here. It’s pretty basic token matching for the areas you highlighted.
I would make all of those new keywords contextual because of backwards compatibility.
Also, ← would be better because colon might make some people think it’s a type declaration when in reality it is inheritance.
So the bacon hair class would look like this:
class BaconhairPlayer <- Player -- Inheritance
static hair = "Pal Hair" -- No override keyword needed & static keyword can be used for making properties or methods static
static username = "UseStarCode_BACON"
end
Out of curiosity, I wanted to see how this holds up with a proper class:
Metatable based approach
local Maid = {__index = {}}
function Maid.new()
return setmetatable({tasks = {}}, {__index = Maid.__index})
end
function Maid:add(task)
table.insert(self.tasks, task)
end
function Maid:clean()
for index=#self.tasks, 1, -1 do
local task = self.tasks[index]
local taskType = typeof(task)
if taskType == "function" then
task()
elseif taskType == "Instance" then
task:Destroy()
elseif taskType == "RBXScriptConnection" then
task:Disconnect()
end
end
self.tasks = {}
end
return Maid
Proposed new syntax
class Maid
constructor()
self.tasks = {}
end
function add(task)
table.insert(self.tasks, task)
end
function clean()
for index=#self.tasks, 1, -1 do
local task = self.tasks[index]
local taskType = typeof(task)
if taskType == "function" then
task()
elseif taskType == "Instance" then
task:Destroy()
elseif taskType == "RBXScriptConnection" then
task:Disconnect()
end
end
self.tasks = {}
end
end
return Maid
I don’t know if this was in the Q&A or not (which weren’t recorded), but I distinctly remember @zeuxcg mentioning plans for this in his talk at RDC 2019.