Luau Switchcases

IMPORTANT

This is ultimately just a proof of concept. Ideally, you’d just use if statements or a lookup table rather than this module. Although I have attempted to make this reasonably performant enough to share as a comparison to other switchcase modules.

Key Features

  • Passthrough support for different cases
  • Lookup table generation for multi-key to value mapping.

Luau Switchcase

This a simple implementation of switchcases into Luau, built in such a way as to maximize reusability. It’s effectively just a wrapper for a lookup table so it’s fairly performant, especially because of my design approach.

Obtain the Module

The latest release of the module’s source can be viewed/obtained from its GitHub Repository.
You can also add it to your project via wally:

SwitchCase = "cens-r/switchcase@2.1.0"

Usage

Unlike typical switchcases in other programming languages, you can reuse the switchcase structure after its construction. This is the primary performance saver of the module as this is always the most expensive step in other implementations of this structure.

Example Code
-- Require the module:
local S = require(PATH.TO.MODULE)

-- For readability sake:
type Switch<T> = S.Switch<T> -- Typing support :D
local switch, case, default, proceed = S.switch, S.case, S.default, S.proceed

type output = {result: string}
local function GenerateOutput(message: string)
    -- Dynamically generating functions for brevity
    return function (out: output, arg: number)
        out.result = message .. ` ({arg})`
    end
end

-- Construct a switch statement:
local statement: Switch<output> = switch {
    -- Fill in your cases:
    case(0) { proceed }; -- `proceed` keyword used for pass-through
    case(1, 2, 3) { GenerateOutput("Low") };
    case(4, 5, 6) { GenerateOutput("Medium") };
    case(7, 8, 9) { GenerateOutput("High") };

    -- Optionally add a default case:
    default { GenerateOutput("None") };
}

-- Use your statement:
for value = 0, 10 do
    local out = statement:resolve(value)
    print(out.result)
end
Example Output
Low (0)
Low (1)
Low (2)
Low (3)
Medium (4)
Medium (5)
Medium (6)
High (7)
High (8)
High (9)
None (10)

As you may have noticed from this code, you don’t need to include break statements like you typically would for switchcases in other languages. This is because the module assumes this is the behavior you want in an attempt to reduce boilerplate. To get this pass-through behavior you’ll need to use the proceed keyword.

There is an option to revert this to standard behavior through a flag within the module:

local S = require(PATH.TO.MODULE)
S.flags.ImplicitExitBehavior = false -- Its default state is `true`

When this flag is set to false you’ll need to explicitly specify when pass-through should NOT happen using the keyword exit.

Note: Both “keywords” (exit and proceed) are values found within the table returned from the module.

Benchmarking

I’ve only done benchmarking against my own previous versions of this module, but the results should be similar (more or less) for any other switchcase module that doesn’t follow a reusable design.

View Image

The details of this benchmark can be found on the repository.

1 Like

I’ll be updating this post with documentation when I get the chance.
I’ve only just realized how difficult it may be to understand what you’re expected to pass into the table given to each case within the switch statement.

To sum it up briefly for now, it expects functions or proceed or exit.
The functions you pass in should match this type:

type CaseCallback<T> = (out: T, arg: any) -> ()

The parameter out is what is a table that is passed between all the methods that are run when resolving (using resolve). So you can pass information between functions within the matched case and functions ran due to pass-through. And this out table is returned from resolve so you can use this information outside your switchcase.

The arg parameter is pretty straightforward. It’s just the value that was passed into resolve.

This from the surface seems like a really good module, however, after some benchmarking it is very apparent that the If-Else method is far, far greater than this.
(5µs vs <1µs, basically a normal if-else statement runs faster than a single microsecond)

Even a normal lookup table would be more performant, as this is just a wrapper on top of that.

It’s a great idea, and somewhat fine execution, but nowhere near enough that developers will want to continuously require this module onto their scripts just to use it rarely. Not only this but there are so many variables and keywords I have to include in the script and memorize them and what they do too.

This module needs a solid reason for developers to use it.

1 Like

If I wanted better syntax on my Lua code, I’d just use MoonScript or YueScript. This just slows down performance

2 Likes

100% agree, and I should probably add a disclaimer within the post.

In terms of cases where this may be useful, its definitely niche. You’d only really “need” this if you require pass-through behavior or want an abstracted method to create a lookup table for numerous alias values.

Outside of the two cases, there’s really no need for this module at all. And my driving force behind creating it wasn’t to replace the standard methods, like if statements, but instead to just expand on an old implementation I had made.

You would absolutely hate my class module. :joy:

local MyClass = {}
MyClass.__index = MyClass

export type MyClass = typeof(setmetatable({} :: {
	foo: string
}, MyClass))
function MyClass.new(foo: string): MyClass
	local self = {
		foo = foo
	}
	setmetatable(self, MyClass)

	return self
end

return MyClass

It’s very sloppily put together but it’s a somewhat pythnoic take on classes for Luau.
I don’t make these expecting people to use them, I just like the challenge of implementing something the language doesn’t strictly support.
GitHub Link

I was working on something like this but then you made this before me now if I make mine public it will look like I am a copycat :sob:

1 Like

You should still make yours public! :smiley:

There’s always room for improvement and innovation and there were a bunch of switchcase modules for Lua/Luau before I even started mine. The key is to do research and figure out where others might lack and what your goals for the project are.

I made some compromises for the sake of reusability for mine (and it’s still not as performant as I’d like it to be), so there are plenty of paths still to be taken regarding the topic as a whole.

2 Likes

We can do some switch case already :


local test  = {
 [1] = function(param)
end,
 [2] = function(param)
end,
}

And it pretty work well

2 Likes

That’s what he mentionned, a lookup table

1 Like

Yep! That’s essentially how the module works behind the scenes as I mentioned. :smiley:

If you need to map a bunch of values to a single function (for example, in a chat command system with aliases) then you start to have a pretty repetitive lookup table which may be harder to read.

Or in the case you need fall through behavior, which is primarily what switchcases provide outside the realm of lookup tables.

Thats where this module shines, although its more of a proof of concept than anything.

Oh ok cool, never use that module, I don’t do big fat… Nevermind… I have done it…

1 Like