Hello, I have made a fully functional Switch Statement that is used in languages such as Java, c++, etc…
Since I want it to be as optimized, organized, and efficient as possible, I am asking for various suggestions on the code
local SwitchStatment = {}
SwitchStatment.__index = SwitchStatment
SwitchStatment.__call = function(self, case)
if case == nil then
case = self.DefaultCase
assert((case ~= nil), "THERE IS NO DEFAULT CASE SET; CASE ENTRY BLANK")
end
goThroughCase(self, case)
end
--Private Function
function getCaseOrder(self, index)
local caseIndexOrder = self.CaseIndexOrder
local currentCaseOrder = caseIndexOrder[index]
assert((currentCaseOrder ~= nil), string.format("THE CASE %s IS NON EXISTANT!", index))
return currentCaseOrder
end
--Private Function
function getCaseIndex(self, order)
local caseOrderIndex = self.CaseOrderIndex
local caseIndex = caseOrderIndex[order]
assert((caseIndex ~= nil), string.format("THERE IS NO CASE THAT TAKES THE PLACE OF %s!", order))
return caseIndex
end
--Private Function
function getNextCaseOrder(self, currentCaseIndex)
local newCaseOrder = getCaseOrder(self, currentCaseIndex) + 1
if newCaseOrder > #self.CaseOrderIndex then return nil end
return newCaseOrder
end
function goThroughCase(self, case)
local caseFunction = self.Cases[case]
local breaked = caseFunction()
if breaked then return end
local nextCaseOrder = getNextCaseOrder(self, case)
if nextCaseOrder then
local newCase = getCaseIndex(self, nextCaseOrder)
goThroughCase(self, newCase)
end
end
function SwitchStatment:GetCase(index)
local cases = self.Cases
local case = cases[index]
assert((case ~= nil), string.format("THE CASE %s IS NOT DEFINED!", index))
return case
end
function SwitchStatment:AddCase(index, func, breakOnFinish)
local cases = self.Cases
local caseOrderIndex = self.CaseOrderIndex
local caseIndexOrder = self.CaseIndexOrder
assert((cases[index] == nil), string.format("THE CASE %s IS ALREADY DEFINED!", index))
local order = #self.CaseOrderIndex+1
caseOrderIndex[order] = index
caseIndexOrder[index] = order
cases[index] = function()
func(index)
return breakOnFinish
end
end
function SwitchStatment:SetDefaultCase(index)
local cases = self.Cases
local case = cases[index]
assert((case ~= nil), string.format("THE CASE %s IS NOT DEFINED!", index))
assert((self.DefaultCase == nil), string.format("THE DEFAULT CASE %s IS ALREADY DEFINED!", index))
self.DefaultCase = index
end
function SwitchStatment.new()
local self = setmetatable({}, SwitchStatment)
self.DefaultCase = nil
self.Cases = {}
self.CaseOrderIndex = {}
self.CaseIndexOrder = {}
return self
end
return SwitchStatment
This is an interesting bit of code, but I am curious as to whether using this implementation actually saves any computational power over using an elseif chain? It looks like a lot of overhead
I feel like a better way to do the above code is this:
local switch = function(case)
return function(cases)
local shouldBreak = nil
local default = cases["default"] or function() end
cases["default"] = nil
for _, possibleCase in next, cases do
if possibleCase[1] == case or shouldBreak == false then
shouldBreak = possibleCase[2]()
if shouldBreak then
return
end
end
end
default()
end
end
Example of it in action.
for i = 1, 4 do
switch(i)
{ -- Since it returns a function, we invoke it again.
{
1, -- Case
function() -- Expression
print(1)
end
};
{
2,
function()
print(2)
return true -- "Break"
end
};
default = function()
print("defaulted")
end
}
end
--[[
Printout:
-> 1
-> 2
-> 2
-> defaulted
-> defaulted
]]
You can always inverse how the arguments are sent. So, you can create a premade switch case then call it throughout your code. That’s more efficient as you aren’t creating the same closures over and over again.
-- Assuming you switched the order the arguments are passed.
local switchStatement = switch {
{
1,
function()
print(1)
end
};
{
2,
function()
print(2)
return true
end
};
default = function()
print("defaulted")
end
}
for i = 1, 4 do
switchStatement(i) -- Same result, but not creating so many function closures.
end
I don’t know how to do this yet because I’m a noob to Lua but I would have thought the simplest way to do something like a switch statement would be something along the lines of…
local switchOptions = {
["case1"] = function()
print("case1")
end;
["case2"] = function()
print("case2")
end;
["default"] = function()
print("default")
end;
}
local selectedOption = "case2";
if switchOptions[selectedOption] ~= nil
switchOptions[selectedOption]()
else
switchOptions.default()
end
As I mentioned I don’t know Lua that well so I’m making this up, but I’m betting something along those lines would be the simplest and most optimal.
It isn’t a switch statement, it’s a literal look-up table (though that’s what switch statements are behind the scenes). I think that’d work out better though.
A key property of good, maintainable code is to reduce the scope of everything, effectively minimizing complexity while maximizing readability, developer efficiency, and maintainability.
Overusing _G is a very bad idea, using it carefully isn’t.
Reasoning
Though a switch function isn’t the best example, it makes sense to expose it globally. It is most likely that it’ll be used throughout the majority of the scripts used. So, cut the need for require and simply using _G.
There is a reason why the standard libraries are globally available in standalone Lua (string, math, io, coroutine, debug). If you know that a module/value is near-universal, might as well make it available globally.
Besides that, in Roblox, _G fields that aren’t loaded by the engine can’t be referenced implicitly. So, it isn’t possible that you’ll be polluting your script’s scope inadvertently.
Though
A better example would be a table of utility functions exposed globally instead of a singular function. It should still exist as a module so that it is still accessible while being transparent where _G.something came from. Thus, while it bleeds beyond its original script’s scope (which, it was going to either way), it is much more readable and efficient.
_G should not be used everywhere and used to make implicit states and pollute _G that should be stuck in its own scope. It should be minimally used for commonly used, top-level variables.
@utrain I like your idea of improving the switch statement as I have never seen the method of lua you have used. The switch statement was originally so post to be some optimized way of dealing with tons of if statements; its purpose is to be initialized and then called upon when necessary, but not redefined.
What I mean is this; You would define the switch at the start of your code during its initialization. Then you would call upon it when necessary.
Since this switch is created at a high level language that has to go through the intrepretor, this switch is useless and it is more effecient to use a yandere dev style stack of if statements. I wish Lua contained such a feature.
Thanks for the compliment. But, I wanted to expand what you said.
You’re correct that a if…else…if… statement would be faster than the switch-statement I made. However, I’d argue it isn’t really a switch-statement, just a pretend version. A switch-statement really can be thought of as a look-up table of memory locations to jump to. But, we don’t have those so, we can do this instead:
Now, note that an invalid case (like 4) wouldn’t default. However, it’s time complexity is now an O(1) instead of an O(n) from before, so it’s now better to use a look-up table instead of nested if … else if … statements. But, it means no breaking. I think that’s an acceptable compromise. Daresay, an advantage but, besides the point.
However, there is a solution to the lack of defaults (plus a way to make life easier)!
local switchCases =
{
[1] = function() print("Case 1") end;
[2] = function() print("Case 2") end;
[3] = function() print("Case 3") end;
}
setmetatable(switchCases,
{
__index = function(tbl) return rawget(tbl, "default") or function() end; -- Only used when nil.
__call = function(tbl, case) return tbl[case]() end; -- Might as well use
}
-- That'll do.
local i = 1
switchCases(i)
-- equal to: ((switchCases[i] or switchCases["default"]) or function() end)()
switchCases[i]() -- This works too.
Now, the plus is that you don’t just have to use numbers. You can use any datatype since Lua is dynamically-typed after all.