Just say “no” to JS switches!
Lots of developers love to hate the switch statement, me included. This is not because the statement doesn’t work or because it’s too difficult.
Understanding how the switch statement works is very easy, the problem is when you run into it in a real-world scenario and you have to stop everything you’re doing to focus on reading it to make sure you’re not missing anything, like a missing break statement that might be causing some unwanted behavior or some 20 lines of code inside a single case of the switch.
The point is, and let me use a fancy term here: the cognitive load required to understand a switch statement (in the real world) is quite heavy. And I believe that we, as developers, so aim to write code that’s easy to read by humans. And in that regard, this particular statement doesn’t help.
But instead of bashing on it and showing you the three same examples on how to avoid it that everyone else is showing you (me included a while ago), let’s take a look at a functional programming technique called: multi-methods.
What’s a multi-method?
He was proposing this concept as a functional replacement to inheritance, for which it absolutely works. And in the process he was showing how the switch statement could be replaced by it as well. So let’s ignore OOP for the time being and only focus on the second part: getting rid of the ugly switch in our code.
So what is a multi-method? It’s just a function that is able to pick the best possible implementation based on the parameters it receives. In other words, imagine if you put your ugly switch statement inside a function and then hid the implementation from everyone.
The only difference though, is that your solution would only work for a single function, and today we’ll be discussing how to generate multiple multi-methods on the fly.
What do multi-methods look like?
And in this language, a multi-method would be used as follows:
There are many things happening in this example, let me explain:
- I’m defining 2 objects, myCat and myDog , they’ll be the ones I use as parameters and the ones that the multi-methods will need to use to decide how to behave.
- I have 2 custom functions, greetDogs, and greetCats I have 2 custom functions, greetDogs, and greetCats and each one has a slightly different implementation. These would represent the code inside each of the switch’s case statements.
- I’m then calling some functions, specifically multi and method to define my multi-method in greeter . The multi function receives 3 attributes: a dispatcher, which is essentially returning the value we’ll be using to discern the right piece of logic to execute and two methods, each one representing a case statement of the switch. Notice how each call to method first specifies the value that would trigger the second parameter (which is where the actual logic resides).
- Finally, I’m using the same function (my multi-method) to execute 2 different pieces of logic without having to write a single switch or IF statement anywhere.
What are the benefits of a multi-method?
Of course, we’re not doing any type of magic here, we’re just re-writing the way we express the decision logic that’s displayed by a switch statement like this:
So why would we go over the trouble of using a multi-method when we can just do that? The point is readability.
The switch statement is very open in showing the implementation of our decision logic. Put another way, this statement is very imperative. It shows you how the inner workings of our decision tree look, and that means that the person reading it will have to parse the code mentally. Hence, we circle back to the idea of the cognitive load. We’re making you, the developer, read and mentally parse the code.
Mind you, most developers won’t bat an eyelid when confronted with a switch like the one above. But of course, that’s also not a realistic example. Usually case statements contain more code and are harder to read.
The multi-method approach however, hides the internals of the decision logic, and all you know is that you’re setting it up and it’ll work somehow. You care more about the functionality than the actual implementation. That’s called “declarative programming” and it helps boost the readability of code while lowering the cognitive load on the developer. That is because it adds a layer of abstraction to the logic, thus providing us with the tools to express ourselves closer to human language.
And if you’re not yet convinced, there is another advantage: extensibility.
If you need to add another option to your switch, you’ll have to go back to the code and modify the same switch , potentially causing problems if you, for example, happened to forget to add a break statement. Like this:
Again, silly example, but with a longer, real-world example, the chances of doing this increase.
In case you’re not familiar with this behavior, the missing break on the first case will cause it to also execute the logic from the second one when the type of animal is “rabbit”.
With multi-methods however, we keep extending it wherever we want:
And now this new extendedGreeter will work on “dog”, “cat” and “parrot”, and we didn’t have to go back and modify already existing code.
This is a huge benefit, since we all know that every time we touch working code, there is a small chance that we’ll introduce a bug. Here we reduce that chance to 0.
If you liked what you’ve read so far, consider subscribing to my FREE newsletter “The rambling of an old developer” and get regular advice about the IT industry directly in your inbox.
Implementing a multi-method library
First of all, just know that there are already libraries out there that take care of this. One example is the @arrows/multimethod one.
That said, it’s always fun to reverse engineer these implementations, so let’s see how we would go about implementing a basic multi-method library to comply with the examples shown so far.
The key to figuring this out, is to understand that we’ll need a dispatcher function to give us the actual value we’ll use as a key to understanding which method to execute. And that we can’t really hardcode a switch statement, since the number of options is not fixed.
Having said that, I give you the implementation:
The method function is just coupling the key with the actual logic, nothing more. The interesting code is inside the multi function, which returns an anonymous function taking the original function and returns a new one that can execute something different based on the value returned by the dispatcher code (our first argument).
Let’s run it step by step:
- First the function from line 8 get’s called with an attribute (let’s say, myDog ).
- On line 9 our dispatcher logic grabs myDog and returns its type, which is “dog”.
- Then on line 10 we find the first method that matches the type.
- If there is no method, but we have a valid “originalFn” (meaning, we’re extending an original multi-method), we’ll let it handle this case. Otherwise, we’ll throw an exception because there is nothing we can do about it.
- However, if there is a method found, on line 18 we execute it and pass it the original attribute “myDog”.
And that’s it. Not that complicated, is it? Of course, you’d have to write a bit more code if you wanted to allow for “default” cases instead of throwing an exception or if you wanted to deal with multi-attribute decisions (like deciding on the logic based on the type and name attributes, instead of just the first one), which is something that a switch would not let you BTW.
But again, if you’re planning on using multi-methods, it’s recommended that you go with one of the existing libraries instead of trying to implement it yourself.
Multi-methods are a fun and interesting way of getting rid of the pesky switch that nobody wants. And it’s also a great opportunity to help the readability of your code. So make sure you give them a try before deciding to stick with switches because you already know how they work.
Have you tried multi-methods in the past? What are your thoughts on them? Leave a comment below and let’s chat!
Build composable web applications
Don’t build web monoliths. Use Bit to create and compose decoupled software components — in your favourite frameworks like React or Node. Build scalable and modular applications with a powerful and enjoyable dev experience.
Bring your team to Bit Cloud to host and collaborate on components together, and speed up, scale, and standardize development as a team. Try composable frontends with a Design System or Micro Frontends, or explore the composable backend with serverside components.
- Building a Composable UI Component Library
- How We Build Micro Frontends
- How we Build a Component Design System
- How to build a composable blog
- The Composable Enterprise: A Guide
- Meet Component-Driven Content: Applicable, Composable
Drop the Switch Statement for this Functional Programming Technique was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.