
Why I prefer objects over switch statements - octosphere
https://enmascript.com/articles/2018/10/22/why-I-prefer-objects-over-switch-statements
======
sklivvz1971
Firstly, switches are arguably way, way (waaaay!) more readable than the code
proposed. It's a matter of taste for sure, but arguably a basic construct of
all curly languages and more is easier to understand than a rather esoteric
code where a pattern is substituted for a keyword. Of course though, it is
true that switch statements in JavaScript have issues with breaks and code
blocks, but any half decent linter will tell you about those.

Most importantly though, the performance profile of the solution proposed
scares me a lot. To understand why, consider that it is not uncommon for
switches to be JITted as:

\- If statements and gotos for small number of options or \- Collections of
lambdas for high number of choices (note, much more optimized than the lambdas
proposed, very likely!)

The reason they are built this way is performance (another commenter
...commented that performance doesn't matter - they are wrong, a switch can be
nested in a hot loop ran millions of times and they do matter). Therefore,
it's easily arguable that the presented pattern will significantly worse in
some cases (few choices) and that should not be underestimated.

~~~
Scooty
> another commenter ...commented that performance doesn't matter - they are
> wrong, a switch can be nested in a hot loop ran millions of times and they
> do matter

I'm curious how often you find yourself dealing with loops that run millions
of times? I think the majority of loops I've written don't need to deal with
millions of iterations; most of them probably only rarely break 1000
iterations, and I know for sure that a lot of them _can 't_ exceed 100
iterations because of limits in the data.

Seems to me that using a switch over another structure for performance at the
expense of readability or maintainability is an example of premature
optimization unless you're positive the condition is going to be in a hot
loop.

~~~
gameswithgo
moderately sized database, pixels on a screen

~~~
Scooty
Both of these seem like cases where you would know you're in a hot loop. I
wasn't really clear in my comment that of course you should optimize loops
that you know get run millions of times. I was saying: pick your tools based
on the context of the code you're writing. Most simple applications involve
tens or hundreds of loops that rarely iterate over more than a few values and
don't get run more than once every few seconds.

------
lkrubner
This wouldn't really be Hacker News unless an arrogant Lisp devotee shows up
and claims that Lisp does everything better, so I'll now try to be that
arrogant Lisp devotee.

Sean Johnson has given a fantastic talk about pattern matching in Clojure:

[https://www.youtube.com/watch?v=n7aE6k8o_BU](https://www.youtube.com/watch?v=n7aE6k8o_BU)

He offers some interesting comparisons between Clojure and Erlang.

Going even further, I recently discovered Dynamatch:

[https://github.com/metasoarous/dynamatch](https://github.com/metasoarous/dynamatch)

" _Dynamatch addresses these challenges by enforcing a certain structure in
our ordering of match clauses such that many of the incidental complexities of
order dependence in a dynamically extensible pattern matching system become
more manageable._ "

Sometimes it seems like Erlang or Haskell has the last word in Pattern
Matching, but I'm not aware of anything like Dynamatch in those languages.

~~~
bastawhiz
No thread on HN would be complete without the JavaScript programmer jumping in
to "well actually" your response, so I formally nominate myself to fill that
role. ECMAScript is getting pattern matching! It's currently a stage 1
proposal with backing from Microsoft, npm, and Facebook.

[https://github.com/tc39/proposal-pattern-
matching](https://github.com/tc39/proposal-pattern-matching)

~~~
amelius
That's interesting, but makes me wonder: how does this spec cover these cases:

\- select for a map with key "a" and not "b"

\- select for a map with key "a" and possibly "b"

\- select for a map with key "a" and possibly any other key.

~~~
bastawhiz
It's the first step into pattern matching, so I'm not surprised that it isn't
as complete as other languages' implementations. But even without further
language modification, I'd bet there are some tricksy things you could do to
replicate that behavior. As the saying goes, "JavaScript finds a way"

------
EpicEng
≥Object lookups are fast and they're faster as their size grow

It's difficult to imagine a switch large enough where the performance
difference would matter, but this ignores the memory required to store the
lookup in the first place. In all of the switch examples a switch is more
straightforward.

In the latter examples (see the Boolean example) we now perform the lookup
twice if the value is present. I feel like this is just another case of "use
the right tool for the job".

~~~
greglindahl
A lot of C/C++/Fortran compilers have very well-developed handling for huge
switch statements because of generated code. Interpreters and other finite
state machines, etc etc.

~~~
EpicEng
Sure. I didn't want to go into too much depth on performance here as I'm a C
and C<variant> guy primarily, only writing JS when I have to.

------
rgoulter
I mis-read the title as meaning a preference for using classes to handle each
case over ML-style switch/case statements.

The JavaScript 'object' here is called "map" or "dictionary" etc. in other
programming languages. (And the article's technique is fine).

~~~
DaiPlusPlus
While you can use any value as an object property key in JavaScript, I’m not
convinced the negative performance tradeoffs are worth the supposed benefits
in the article.

I appreciate `switch` - with its case-fallthrough surprises beginners but most
C-style languages all share this quirk - and modern-day compilers and linters
will gladly remind you that usung Duff’s Device-type tricks in JS don’t work.

As an aside, in C#, a string-based switch statement is actually compiled to a
lazily-initialised hidden Dictionary<String,Int32> object where the values are
the real integer case values - so kinda similar to the linked article - except
without the runtime possibly reallocating and reinitialising the dictionary
object on every invocation.

~~~
beatgammit
This is one nice thing about Go, which uses explicit fall through with break
being there default. You can still fall through if you want.

However, I've come to love pattern matching in Rust. It prevents fall through
entirely, which makes it unnecessary to check if the code is doing something
clever with fall through, which makes the code more simple to parse.

------
rovolo
The author's basic complaint with the Javascript switch statement is that it's
essentially a structured series of 'goto' statements. They think that each
'case' should behave as an 'if-else' where each case has its own lexical
scope, and the control flow doesn't leak from one case to another. They think
that pattern matching is a better model for 'switch' in practice.

How often do you use a 'switch' statement whose cases don't always end in
'return' or 'break'? The "coroutines in C" [0] article is a clever use of
switch-case as a goto, but it seems like you need to invent new types of
control flow to use 'goto' properly. Does anyone have other clever uses of
'switch'?

[0]
[https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html](https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html)

~~~
ricardobeat
This is my favorite from a library I wrote ~6 years ago:

[https://github.com/ricardobeat/require-
tree/blob/master/inde...](https://github.com/ricardobeat/require-
tree/blob/master/index.js#L16-L22)

The goal is to accept a 'filter' argument that can either be a string, an
array of strings, a regular expression, or a filter function. It fully uses
fall-through and the multiple entry points. I find it magical, in that it
turns all of those into a function so the remainder of the code doesn't have
to care, and it's not any less efficient. Similar feeling to finding a use
case for 'Infinity' :)

    
    
        switch (type(filter)) {
            case 'array'    : filter = filter.join('|')
            case 'string'   : filter = new RegExp('^('+filter.replace('*', '.*')+')$', 'i')
            case 'regexp'   : filter = filter.exec.bind(filter)
            case 'function' : return filter
        }

~~~
fc88
This is one of the most amazing pieces of code I have ever seen

------
crazygringo
I mean, they're both useful for different purposes.

When you're dealing with 4 possible values, each of which will result in
wildly different code (e.g. evaluating the value of a options variable, or
handling error codes that mean very different things), then switch is clearly
the way to go.

When you're dealing with 20 or 200 different values, all of which map to a few
similar variations, then defining an object or array lookup is clearly
preferable.

"Preferring" objects over switch statements is like saying you prefer bitmaps
over vector drawings -- it's nonsensical. Different tools are better for
different jobs.

------
tombert
I remember getting in arguments when I was doing F# over my use of Maps of
functions to basically handle dynamic dispatch. I would construct a map like
`let handlers = [| "somekey",func1; "otherKey", func2 |] |> Map.ofArray`, and
then when I needed to handle something down the line, I would write something
along the lines of:

let myHandler = defaultArg (Map.tryFind theKey handlers) (fun x -> //default
stuff)

myHandler theValue ....

I liked this approach, since I could dynamically add functionality, and it
could be completely decoupled from the business logic, and I didn't have to
use strings for keys, but my coworkers didn't like that how dynamic it was,
since in fairness, it did sometimes make it a bit more difficult to figure out
which path the code was going to go down.

Never really determined who was "right" in this case, but this post reminded
me of that.

------
tasty_freeze
I wrote an old computer emulator in javascript. The inner loop is a large
switch statement where each case handles one of 256 opcodes. Firefox handled
it fine, but chrome performance was poor. It turns out that above a 128-way
switch, the jit gives up (or it seemed to).

First I tried doing what the author suggested -- having 256 routines, and a
dispatch table. Chrome performance got better, and Firefox performance got
worse.

In the end, the fastest thing to do was to have "if (opcode < 128) { 128-way
switch } else { 128-way switch }".

That was 2014, so likely things have changed.

~~~
yosser
Ironically, that's precisely how a lot of old Z80 and 6502 programs managed
conditional execution. We used to call it a 'jump table'.

------
tengbretson
Alternatively titled: "How to go to war with your linter, type checker and
other static analysis tools and win"

------
oflannabhra
In Swift, switch is my new favorite tool, combined with Swift's enums and
amazingly strong native pattern matching. It has absolutely changed how I
write code, and makes me wish it were a tool I could reach for whenever I'm
working in another language. Some examples:
[http://austinzheng.com/2014/12/16/swift-pattern-matching-
swi...](http://austinzheng.com/2014/12/16/swift-pattern-matching-switch/)

~~~
paultopia
Came here to say this. Many of the complaints about switch statements are
about classic, kinda terrible, implementations of switch statements. Swift
gets that right. Like, really super right.

------
bigtech
The article provided some good examples of times when code readability can be
increased without a bulky switch statement. However, there are times when I
find the switch statement most closely communicates the idea of what needs to
occur to some developer in the future.

------
ricardobeat
As someone who has migrated between heavy use of these patterns in the past
(object -> switch), I'd like to provide a few counterpoints.

First, and this is more of a general observation for any kind of programming
content, these pompous-sounding abstract value judgements need to stop:

    
    
        1. Is more structured.
        2. Scales better.
        3. Is easier to maintain.
        4. Is easier to test.
        5. Is safer, has less side effects and risks.
    

Regarding `switch`, only the last is a fact and that's because of the `break`
statement peril. Still there aren't really side effects or other 'risks'
involved. Everything else is completely subjective and not supported by the
examples above - I, for example, find switch easier to maintain as you don't
need to juggle variables defined outside the object to keep it clean.

Second, these articles use innocuous examples that don't reflect real use
cases, and hence fail to demonstrate their utility. You'll find a ton of
switch statements in any kind of parser since it's the perfect construct for
the occasion where each branch can wildly differ in content and complexity,
and might embed flow control that would complicate the object-based version:

    
    
        switch (node.type) {
          case "Identifier":
          case "ObjectPattern":
          case "ArrayPattern":
            break
    
          case "ObjectExpression":
            node.type = "ObjectPattern";
            for (var i = 0; i < node.properties.length; i++) {
              ...
            }
            break
    
          case "ArrayExpression":
            ...
        }
    
    

Finally, `switch` is wonderful when paired with `return`, since it eliminates
point 5 above. Sample taken from a project I have lying around:

    
    
        switch (unit) {
            case 's': return value * 1000;
            case 'm': return value * 1000 * 60;
            case 'h': return value * 1000 * 60 * 60;
            case 'd': return value * 1000 * 60 * 60 * 24;
            default : return null;
        }
    

With the key lookup, you'd also end up precomputing all of those values
(imagine that's a slightly more expensive operation than simple math), or
turning each one into a function. Another good example is the state reducer
pattern:

    
    
        switch (action.type) {
            case 'ADD':
                return state.concat(action.payload);
            case 'REMOVE':
                return state.filter(item => item.id !== action.payload.id);
            default:
                return state;
        }
    

The key lookup pattern can hold its own in the simple cases, but it's hard to
justify it with anything more than stylistic preference.

~~~
kaizendad
This is exactly correct. The cost of holding the entire object in memory, for
any significantly complex statement, is going to add up quick if you're
dealing with large numbers of users/pageviews/etc. The cost of pre-calculating
everything in the object for any complex math similarly gets large when you're
dealing with something large-scale. Early returns matter a _lot_ with scale,
_and_ make [code line of sight]([https://medium.com/@matryer/line-of-sight-in-
code-186dd7cdea...](https://medium.com/@matryer/line-of-sight-in-
code-186dd7cdea88)) much clearer -- which matters a lot of if your team scale
is larger, as in many companies that might have multiple teams working on one
shared codebase.

------
yongjik
I don't know javascript, but in the proposed "structured" code, doesn't it
execute initialization statements for _every potential cases_ even when you
call it only once? I.e.,

    
    
        const getValue = type => {
            const email = () => 'myemail@gmail.com';
            const password = () => '12345';
            ....
    

Now "const email = ..." will be executed _every time_ even when you're just
asking for password. Eventually, such a code "scales", become a bloated
behemoth with twelve cases, called a hundred time deep inside a server,
initializing everything every time it is called, with potential side
effects...

...and then one day a starry-eyed new hire looks at the top-level code,
thinking "Heh, this is an internal graph server, why does it need customer
email addresses?", removes the top-level config line, and then suddenly all
internal dashboards go blank because they can't read email addresses.

...Yeah, you can probably tell that I'm not a fan of this technique.

------
bitwize
Fun fact: There's an optimization technique for OO languages called
polymorphic inline caching that boils down to... a switch statement that
speeds up method dispatch by branching directly to a method implementation for
one of a few common types. If the object is none of those types, it falls
through to a more conventional method lookup.

------
jwr
The discussion is missing the most important point, IMHO: readability and
maintainability of the larger system in the long term.

I used to love objects and multimethods (or single-dispatch multimethods for
those more limited languages). But then I ended up debugging a large code base
which used them extensively. It is a nightmare: by reading the code, there is
no way to find out what all the dispatch options are, and without
interactively debugging it there is no way to see which code will get called
(inheritance messes things up greatly).

I think performance is secondary to these problems, so these days I prefer
switch statements (or pattern matching), for their simplicity and reliability.

------
androidgirl
I've never used this pattern in Javascript, but it's somewhat common in Python
because of how handy the dictionary.get() method is. A few problems TFA solves
in JS are much easier with .get(), like defaults and false values.

~~~
UncleEntity
Plus the fact that python doesn't have a switch statement so you kind of have
to use a dict if you want that functionality without a long chain of
if/elif/else.

------
dewaine
This is some kind of funny joke article

------
maxxxxx
Isn't this basically a convoluted way to end up with a dictionary or command
pattern?

------
perfunctory
Objects vs switch is not either/or. They both have valid use cases.

------
makz
I don’t know, feels like a step back to me.

It could be as well lookups on a hashmap.

Not very expressive.

------
ravenstine
I don't think any one pattern rules out any other, but this is a good one for
people to keep in mind as I think sometimes it makes more sense. Heck, there
are even some cases where an object lookup makes more sense than an if-then-
else statement.

I'm also a fan of other patterns like early returns, but few people ever seem
to do it.

~~~
smhenderson
I like early returns as well, especially in short functions where there is not
a lot of code between returns. In other words if I can see all returns in a
function on one screen of text then I'm OK with it and I think it improves
readability.

On the other hand if I have to scroll up and down a lot to see all return
paths then that's a problem. But I generally find the problem is that the
function is too long/does too much and should be broken down into smaller
pieces.

------
koboll
Another compelling argument for the same position:

[https://toddmotto.com/deprecating-the-switch-statement-
for-o...](https://toddmotto.com/deprecating-the-switch-statement-for-object-
literals/)

------
fallingfrog
Ah, but can objects be used to implement Duff's Device?

~~~
jerf
Kinda-sorta:

    
    
        function psuedoduff(count) {
            var x = 0;
            var n = Math.floor((count + 7) / 8);
            var o = {
                0: function() { x++; o[7]() },
                7: function() { x++; o[6]() },
                6: function() { x++; o[5]() },
                5: function() { x++; o[4]() },
                4: function() { x++; o[3]() },
                3: function() { x++; o[2]() },
                2: function() { x++; o[1]() },
                1: function() { x++; n--; if (n > 0) { o[0]() }},
            }
    
            if (count > 0) {
                o[count%8]();
            }
    
            return x;
        }
    

That just increments to show the point, it would be easy to make it do some
sort of real work.

Of course you'll run out of recursion at some point. Trampolining could fix
that. I leave it as an exercise for the reader whether that makes it more or
less silly.

I'm also of course aware that Duff's device doesn't make function calls, to
which I vigorously handwave while chanting "Monads Are Programmable
Semicolons" in my "Lambda The Ultimate" T-Shirt.

Lambda.

------
yoz-y
For mapping from one value to another, sure. My irk with this solution is that
none of their "object" solutions actually does the same thing as the switch
statement, namely logging the state in various manners to console.

Now, they could, of course, do it using the function method, but they did not.
If the functional code had logs in them it would become quite apparent that a
bunch of if-else statements would probably do the job better.

------
nameiscubanpete
Speaking of code readability, the article uses a function in the example
called "isNotOpenSource()". If you have a function that returns a boolean,
it's best not to put another boolean in the function name i.e.
"isOpenSource()" is cleaner and more readable.

Honestly, it's a code smell that makes me trust my initial reaction that the
switch statement is indeed more readable than the authors solution.

------
jpochtar
Take it one step further, and move the switched-over value to the top:

    
    
      ((o) => o[expr_to_switch_over()])({
        opt1: () => {
          stmts();
        },
    
        opt2: () => {
          stmts();
        },
    
        opt3: () => {
          stmts();
        }
      })

~~~
ezekg
And then you can call it a “switch statement”!

------
chriswarbo
I like doing this, and find it fits reasonably well with a functional
programming style: it is to `switch` what ternaries are to `if/then/else`
(i.e. a nice way to select values; not necessarily nice for executing
statements).

------
8lall0
You type far more code (which in JS matters A LOT) for a less flexible
solution and for such a simple task.

I completely disagree.

------
aaaaaaaaaaab
I pity the devs inheriting his code.

------
dickeytk
I love this, but I feel TypeScript would be very unhappy with such a thing.

~~~
alwaysdoit
Between TSLint's no-switch-case-fall-through and an exhaustiveness check[1] I
don't think any of these things are really problems with switch statements in
TypeScript

[1] Putting a function like this in a default case will enforce that all cases
are declared at compile time:

    
    
        function exhaustive(exp: never): void {}

------
chaoticmass
I prefer if/elseif over switch statements.

ok, ok, I'll go sit in the corner.

------
camgunz
Polymorphism vs pattern matching: fight

