Tables As Pseudo-Switch Statements Over Lengthy/Nested Elseif Statements

Hey all! Sorry in advance if the tone of this post comes off as a little frustrated, I’ve just been racking my brain on this for a couple of years at this point and it seems like nobody giving out advice on this subject actually has any idea what the point of a Switch Statement is.

Since Lua has no native Switch Case support, and every answer I’ve found from other sources have left me dissatisfied (they ALL wind up using elseifs and let whatever program they’re using determine which case to use in some way or another), I’ve decided to just try and come up with my own.


Say we want to turn a character Left or Right using a single function in ContextActionService…

A pretty common way I've seen it written out looks something like this...
function Turn(action, state)
	if action == 'Left' then
		if state == Enum.UserInputState.Begin then
			turnDir += 1
		elseif state == Enum.UserInputState.End then
			turnDir -= 1
		end
	elseif action == 'Right' then
		if state == Enum.UserInputState.Begin then
			turnDir -= 1
		elseif state == Enum.UserInputState.End then
			turnDir += 1
		end
	end
end

It works, but it looks rather messy and there’s a bit of repetition going on (repeated references to the same variable, virtually identical nested if statements), and performing checks like this causes whatever is doing the checking to perform in serial, comparing each and every argument down the line until it reaches one that is true.


Many devs – both on-site and from external sources like StackOverflow – would also have you split functions up and continue to use if statements (ternary or otherwise).

The method many people like to use to emulate a switch statement usually goes like this...
function TurnCases(state)
	if state ~= Enum.UserInputState.Begin and state ~= Enum.UserInputState.End then return end

	return {
		Left = if state == Enum.UserInputState.Begin then 1 else -1;
		Right = if state == Enum.UserInputState.Begin then -1 else 1;
	}

end

function Turn(action, state)
	local case = TurnCases(state)

	if (case) then
		turnDir += case[action]
	else
		turnDir += 0
	end

end

Honestly, this looks even worse. Not only is there a bunch of redundant garbage clogging up memory, it still does nothing to reduce the amount of instructions – if anything, it adds more overhead.


And so, after some tinkering of my own, I present to you:

An actual switch statement in Lua.

Ex. 1 (as a variable):
function Turn(action, state)
	turnDir += ({
		[state ~= Enum.UserInputState['Begin' or 'End']] = 0; -- Default
		
		[action == 'Left' and state == Enum.UserInputState.Begin] = 1; -- Cases
		[action == 'Left' and state == Enum.UserInputState.End] = -1;
		[action == 'Right' and state == Enum.UserInputState.Begin] = -1;
		[action == 'Right' and state == Enum.UserInputState.End] = 1;
	})[true]
end
Ex. 2 (as a function):
function Walk(action, state)
	({
		[state ~= Enum.UserInputState['Begin' or 'End']] = task.wait; -- Default

		[state == Enum.UserInputState.Begin] = function() -- Cases
			moving = true
			while task.wait() do
				if not moving then break end
				local rotation = rootPart.Orientation.Y
				Xdir = if action == 'Forward' then -math.sin(0.0174532925*rotation) else math.sin(0.0174532925*rotation)
				Zdir = if action == 'Forward' then -math.cos(0.0174532925*rotation) else math.cos(0.0174532925*rotation)
			end
		end;
		[state == Enum.UserInputState.End] = function()
			if keyHeld('W') or keyHeld('S') then return end
			moving = false
			Xdir = 0
			Zdir = 0
		end;
	})[true]()
end

(v Edit v)
It’s best to construct the switch tables outside of their respective functions, unlike what I did with my little rush job, as @sleitnick helpfully pointed out in his reply below.

(^ Edit ^)

Since every case is inside the table, including the default, there’s no need for any sort of iteration through each case. There is only one case in the table that can be true at a time, and that case gets pushed up to the top, therefore:

  • No unnecessary checks need to be made.
  • No questions need to be asked.
  • No more wondering why you got the wrong result due to some misplaced check.

“Are we pushing a button? Are we releasing a button? Are we supposed to go a certain direction? Which direction? Yes? No? What am I supposed to be doing?” – It doesn’t matter.

Everything is laid out in simple “WhenActive → DoThing” terms

Computers aren’t very smart. It’s not a good idea to let them decide for themselves most of the time. YOU TELL THEM what to do. At least until AI takes over all the important management positions.

“You’re pushing X button, so I’ll move in Y direction.” Simple as.


NOTE: If statements definitely still have their uses. They’re often much better and faster at doing very small-scale checks (E.G. you’re just checking if a player’s health is above, below, or has reached a certain value).

HOWEVER: For deeper, more advanced checks, the constant lookup time of a table has far more benefits than it does drawbacks.
For instance: The result of clicking a button in a menu with 2 or 3 dozen options would be displayed much faster with a switch statement than it would with an if statement. Same as any scenario in which you have to compare a lot of values to an argument.


I think it goes without saying that a little more consistency and a little less repetition makes for a smoother experience, both for the user and the person writing the code.

Thank you for coming to my Ted Talk


Other edits: spelling/grammar

4 Likes

Some thoughts:

  • So a lot of compiled languages will actually take large switch statements and treat them similar to a constant-time hash lookup. Importantly though, this lookup is created at compile time.
  • In Lua(u), you’re allocating memory every time you create a table, creating garbage that the GC has to then keep track of and delete later.

Doing some benchmarking, an if/elseif chain of 10 choices, where the last choice is correct, still performs 6x faster than a table switch, and 15x faster when run in native mode. However, this flips if the table is constructed once beforehand and is reused. In that case, the table switch is slightly faster in both modes.

4 Likes

Right. I just sort of shoved the tables into the functions for brevity’s sake. That would be how I’d lay this sort of thing out in practice.

Some sort of angry catharsis led me to structure the statements in the way that I did lol

“IT WORKS, DANG IT. LOOK.”

I’m curious to see if the Luau team ever finds it valuable to add switches into the language itself. Would be pretty neat if they did!

2 Likes

Same here, but then again, I’d also like to see how many licks it takes to get to the center of a tootsie pop

I was just getting curious about switch statements in Lua, how convenient that there’s a topic I don’t have to necropost or that’s severely outdated, anyways, I haven’t seen someone do switch statements in Lua with tables like I do before.

Good read, and god pray Roblox does add switch statements as they are far cleaner for state based conditionals than if statements are. And could potentially be optimized as they are, as nick said, often based on hash tables with constant time lookups, which could benefit in some of those incessantly long statements one will find or write themselves from time to time.

Also as a child, I got bored enough to actually find out how many licks it took. It took me I believe 260 licks, but I could be remembering wrong. Somewhere in that range.

1 Like