[HN Gopher] Using the switch(true) pattern in JavaScript
___________________________________________________________________
Using the switch(true) pattern in JavaScript
Author : seanbarry
Score : 97 points
Date : 2021-04-12 08:22 UTC (14 hours ago)
(HTM) web link (seanbarry.dev)
(TXT) w3m dump (seanbarry.dev)
| elyseum wrote:
| A good example of: just because you can, it doesn't mean you
| should. Yes, the if/else sequence is a bit more unstructured in
| terms of code layout, but chances the next developer browsing the
| code will instantly know what it does will be much higher.
| TeMPOraL wrote:
| The only problem here is that you have to do this in the first
| place. This problem has been solved some 60 years ago with Lisp
| and `cond` construct, which is even older than `if/else
| if/else`! Alas, our industry likes to forget history.
|
| That said, this isn't some kind of high-level hackery. If it's
| cleaner than if/else if chain, you should use it.
|
| Programming is a profession. A professional is expected to
| learn and grow. The right answer to seeing a code construct one
| doesn't understand isn't throwing hands up in the air, but
| spending the few minutes necessary to figure out what it does.
| TheHalfDeafChef wrote:
| I consider myself an advanced-beginner programmer untested in
| the real world (looking for my first coding job though!), and I
| understood it relatively quickly. It does appear more concise
| than many if-else statements. After the initial "what?", I was
| able to quickly parse through it. I think this is faster than
| using the standard form.
|
| [edit: fixed typo]
| arielserafini wrote:
| "faster" in which respect?
| TheHalfDeafChef wrote:
| Faster to parse as a developer. I can't speak to whether
| computationally it is faster.
|
| Additionally, doesn't this somewhat also bring in the
| methodology of dealing with the error cases first?
| [deleted]
| t-writescode wrote:
| A lot of the code you're going to have to be dealing with in
| the wild is going to look fine, until it breaks at 2am on a
| Saturday and you're 3 drinks in and production is down.
|
| You're going to hate your former cleverness then.
| TeMPOraL wrote:
| There should be a rule that forbids applying this reasoning
| to code that isn't triple-nested template SFINAE mess, or a
| code-generating-code-generating-code-generating-code Lisp
| macro.
|
| This pattern isn't "cleverness", it's just a readability-
| improving trick that's cheap to figure out. The hundred
| seconds you spend on it seeing it for the first time is a
| one-time cost.
| tantalor wrote:
| That's why I abstain when I'm oncall (or backup).
|
| That's what the oncall bonus pay is for.
| t-writescode wrote:
| Replace alcohol with 2 hours of sleep, then :)
| csmattryder wrote:
| Spare a thought for the junior/mid-level devs who don't
| understand clever patterns, and the seniors who haven't read
| the same blog posts as the implementor of the switch(true).
|
| Completely agree, it's like writing readable prose, understand
| your audience.
|
| That said, give me proper pattern matching in JS. Hard to live
| with languages that haven't caught up yet.
| nicoburns wrote:
| > That said, give me proper pattern matching in JS. Hard to
| live with languages that haven't caught up yet.
|
| I really want this. Along with switch being as expression.
| Seems like something that JavaScript is obviously missing.
| jessaustin wrote:
| _I really want... switch being as expression._
|
| Unfortunately JavaScript still hasn't caught up with
| CoffeeScript.
| deergomoo wrote:
| > Along with switch being as expression
|
| Even PHP has this now, albeit with a different keyword
| (match) due to existing switch semantics being pretty bad
| harg wrote:
| I think the post doesn't give a fair comparison as in the if/else
| case there's no need to use "else" blocks if you're throwing an
| error or returning. In this case I think simple "if" statements
| are cleaner and certainly more "normal".
|
| E.g. if (!user) { throw new Error("User
| must be defined."); } if (!user.firstName) {
| throw new Error("User's first name must be defined"); }
| return user;
| CloselyChunky wrote:
| When validation gets complex (e.g. there are many criteria to
| check), I like to build a list/stream/array (what ever the
| language offers) of tuples of predicates (functions from the
| object that gets validated to boolean) and strings (or
| functions from the object to string so I can have context in my
| error messages).
|
| Then iterate over the tuples, if a predicate fails, return the
| associated error message and throw an error/display the message
| to the user.
|
| In the end it looks something like this: var
| validators = Stream.of( Map.entry(user -> user != null,
| "User must be defined"), Map.entry(user ->
| user.firstName != null, "Missing first name"))
| validators.filter(e -> e.getKey().apply(userToBeValidated)).map
| (Map.Entry::getValue).getFirst()
|
| (This example uses Map.entry for tuples as Java lacks native
| support for tuples)
|
| This limits branching and you have all validation criteria
| neatly organized in the same location.
| harg wrote:
| Sure, if you're validating some data there're loads of better
| ways to do it than a bunch of conditionals. I was purely
| commenting on the switch(true) pattern vs some "if"s.
|
| That approach looks nice though. On that subject, JS has some
| nice libraries including io-ts[1] which has a functional
| approach using Eithers to encapsulate errors/success.
|
| [1]: https://github.com/gcanti/io-ts
| CloselyChunky wrote:
| Sure, if you have an either/result type, the whole thing
| becomes a fold, where each validator is a function from
| user to Either<Error, User> and then
| validators.reduce(Either.right(user), (acc, next) ->
| acc.flatMapRight(next))
|
| This way you'll end up with either the validated user, or
| the first error that occurred and all the other validators
| were skipped.
| wvenable wrote:
| If you are using a language with yield you make this simpler
| (and more flexible) by using a generator.
| fl0wenol wrote:
| Agreed, +1 This pattern goes beyond JS and is very helpful
| for supporting tests; you've already factored out your
| validators. There's always going to be stragglers but you can
| define and push in an extra check on the spot (i.e. using
| lambda funcs at least in JS).
| _greim_ wrote:
| I'm definitely in the minority here, but I'd rather see the
| `else` block, since it's more explicit at-a-glance, and the
| logic has more symmetry with all outcomes on the same level.
|
| One reason I like RustLang is it treats this as an ergonomic
| issue by appending `?` for early returns, without block-
| nesting. So nice.
| molszanski wrote:
| In addition to an already mentioned early return pattern, I also
| often recommend switching from switch to pattern matching via an
| object.
|
| It has an additional benefit of extracting code into data
| structures or making them parametric function
| getArrow(direction) { switch (direction) { case
| "left": return "<--" case "righ":
| return "-->" default: return "-\\_(tsu)_/-"
| } } function getArrow(direction) { const
| arrows = { left: "<--", right: "-->",
| default: "-\\_(tsu)_/-", } let arrow =
| arrows[direction] if (arrow) { return
| arrow } else { return arrow.default }
| }
| graderjs wrote:
| Guaranteed to always occur: ... case
| !isValidPhoneNumber(user.email): ...
|
| Tho see [1] and [2]
|
| [1]: https://github.com/kdeldycke/awesome-falsehood#emails
|
| [2]: https://github.com/kdeldycke/awesome-falsehood#phone-numbers
| seanbarry wrote:
| Careless typo - thanks for flagging ;)
| dvaun wrote:
| What a great set of resources. Thank you for sharing this repo!
| 8128js8121 wrote:
| ``` switch (true) { case 1 + 1 === 2: // This case evaluates to
| true so it will be executed default: // This will not be executed
| } ```
|
| This is wrong, Since there is no return or break default will
| also be executed.
| dvirsky wrote:
| Reminds me of a C++ pattern some guy I worked with years ago used
| to love to simplify complex if checks - using do...while(false)
| to create a scope you can easily break out of. e.g.
| bool success = false; do { if (!someCondition)
| break; if (!otherCondition) break; ...
| success = true; } while(false); if (!success)
| { ... }
|
| I personally disliked it, plus it can lead to funky behavior
| under optimization.
| [deleted]
| standardUser wrote:
| On a similar topic, I'm wondering how often people are using the
| "else" part of if/else these days. I haven't written "else" in
| years and I've become very fond of that "if only" pattern.
| alerighi wrote:
| If you have a chain of conditions what you do?
| if (condition1) { // something }
| if (!condition1 && condition2) { // other stuff
| } if (!condition1 || !condition2) { //
| finally }
|
| An if-else is more clear: if (condition1) {
| // something } else if (condition2) { //
| other stuff } else { // finally }
|
| To me if-else is more easy to reason about (since it's clear
| that you enter in one of the 3 possible branches without even
| looking at the conditions), but also it's more efficient,
| especially if the condition is not a trivial comparison (for
| example you are comparing strings, or doing some other linear
| operation. And yes, computer are fast these days, but there are
| no excuse for wasting resources for nothing to me).
| TeMPOraL wrote:
| If that if/else chain is returning a value, then what's even
| more clear is: if (condition1) {
| return something; } if (condition2) {
| return otherStuff; } return finally;
|
| I don't remember the last time I wrote an if/else chain that
| wasn't in return position.
| jbverschoor wrote:
| Often enough, and I use if you capture paths that should never
| happen logically.
| kvczor wrote:
| I prefer early return pattern.
|
| Which would look like this: const user = {
| firstName: "Sean", lastName: "Barry",
| email: "my.address@email.com", number:
| "00447123456789", }; if (!user) {
| throw new Error("User must be defined."); }
| if (!user.firstName) { throw new Error("User's first
| name must be defined"); } if (typeof
| user.firstName !== "string") { throw new
| Error("User's first name must be a string"); }
| return user;
| btown wrote:
| I agree; `if (` is much more well-known and "grokkable" to
| engineers at practically any level than `case` - and exactly
| the same number of characters. There's no need to bring a
| sledgehammer when the nail is perfectly handled by a normal
| hammer.
| layer8 wrote:
| I agree, but the problem presumably is the "else".
| cced wrote:
| I think the issue here though, is that for certain cases such
| as form field validation, you want all issues to be returned at
| once. Using the switch method or similar message packages allow
| you to inform your users that have N issues with a page in 1
| request as opposed to N.
| planb wrote:
| That's not how throw works. And that's exactly why the
| switch(true) pattern should not be used: novice users not
| fully knowledgeable about every specification of a language
| should not fail to understand such a basic piece of code.
|
| There's not even a line difference to using if statements
| correctly (as others in the comments have demonstrated) The
| one thing an if statement does is checking if something is
| ,true'. I don't understand why anyone would use a ,switch'
| here apart from showing how clever they are.
| Sayrus wrote:
| Either you will break, thus exiting the switch-case block or
| you will fall-through. The fall-through behavior won't do
| what you want:
|
| > the script will run from the case where the criterion is
| met and will run the cases after that regardless if a
| criterion was met.
|
| Using the switch method won't allow you to return several
| errors while the simple ifs method described earlier could
| accumulate the errors and return later. The switch method is
| elegant though.
| thomas_moon wrote:
| The early return pattern was the most effective single piece of
| advice I received from a senior dev on how to make my code more
| readable. You end up with clearer code paths than any other
| pattern I have seen so far and way less indentation making
| things look (and probably perceived) as less complex.
|
| Pair it with naming your complex and chained expressions and
| suddenly you have some seriously readable code.
|
| So far, I have never seen a valid scenario where a switch
| statement is actually any better than if.
| [deleted]
| mekster wrote:
| Unwanted conditions should be handled before handling the
| process in a peaceful condition.
|
| Not sure it makes much sense to indent everything within "if"
| and if you forget to "else", you've just potentially hidden a
| bug.
| anyonecancode wrote:
| I think the early return pattern makes the most sense when
| it's less "logic" and more "validation." Eg in the example,
| the code isn't really _doing_ anything if there's no user or
| no first name, it's running a bunch of checks _before_ trying
| to do anything. Those validation checks are implemented with
| if/early return, but the actual pattern is more "validate
| first and only proceed if everything checks out."
|
| In typed languages a lot of those checks get implemented in
| your actual types, but you still might have various business
| logic/data integrity checks you might implement in early
| return.
|
| Seen in this light, this pattern's really not so much in
| tension with the idea of having a single return variable.
| It's just a way to implement the idea that invalid states
| should not be possible, which you accomplish in your type
| system when and if possible, and fall back to runtime checks
| for the gaps where it's not.
| jandrese wrote:
| Early return can get into trouble if you're talking about a
| function that has a fair bit of state to unwind in the event
| of an error. Open filehandles, allocated memory, general
| cleanup stuff.
|
| That said, I much prefer early return whenever it makes
| sense. In functions that do have a lot to unwind and many
| possible points of failure I'll pull out the old villain
| 'goto' and have the unwind code at the bottom of the
| function.
|
| Strictly sticking to only one approach is usually a mistake.
| One that is repeated a lot in computer science. There are
| schools of thought that if everything is the same it will be
| easier to understand, but what happens is problems that don't
| exactly fit the mold end up being solved in awkward and
| inefficient ways. Or development gets slowed because you have
| to refactor your problem around the tools instead of the
| other way around.
| Jtsummers wrote:
| > Early return can get into trouble if you're talking about
| a function that has a fair bit of state to unwind in the
| event of an error. Open filehandles, allocated memory,
| general cleanup stuff.
|
| This is where Lisp's _unwind-protect_ , _finally_ blocks in
| some languages, _defer_ in Go, and C++ 's RAII pattern can
| come in handy. Especially if you have a healthy respect for
| goto and want to minimize its presence in your code for
| various reasons.
| golergka wrote:
| `finally` introduces additional blocks, so if you have 5
| different resources that you acquire as your function
| goes along, you end up with 5 levels of curly braces. Not
| very elegant.
|
| `defer`, on the other hand, is probably the only thing I
| wanted to take from Go to other languages I worked with.
| Beautifully explicit and wonderfully useful.
| brundolf wrote:
| Unpopular opinion: I think early-returns make code less
| readable if you don't put the rest of the function in an else
| clause. You lose visual parallelism, and suddenly instead of
| following a tree down to a series of leaves where every leaf
| terminates, you have to have the full context to know whether
| or not a line of code may not execute in some cases. For
| example: function foo(x) { if (x ==
| null) { return null; } x += 2;
| return x; }
|
| I can't just look at "x += 2;" and know whether or not it's
| conditionalized. I have to have the full context including
| the early-return, and then reason about the control flow from
| there. Whereas: function foo(x) { if
| (x == null) { return null; } else {
| x += 2; return x; } }
|
| Here I can tell just from the else-block that this is one
| possibility which will execute if the other one does not, and
| vice-versa. Their indentation is the same, cementing their
| relationship. If I want to know whether this block will
| execute I need only look at the if()'s, not their contents.
| tpoacher wrote:
| I agree, but also I think this is misrepresenting the
| 'early return' pattern. It's more likely to be succinct and
| look like this instead: function check(x)
| { if (!test1 (x)) return false;
| if (!test2 (x)) return false; if (!test3 (x))
| return false; if (!test4 (x)) return false;
| return true; }
|
| In _this_ scenario, it is true that this is much more
| readable than: function check(x) {
| if (!test1 (x)) { return false;
| } else if (!test2 (x)) { return false;
| } else if (!test3 (x)) { return false;
| } else if (!test4 (x)) { return false;
| } else { return true; }
| }
| tsimionescu wrote:
| Never ever use if without curly braces. That's one of the
| worst ideas of C's syntax.
|
| Your first example would still look clean like this:
| function check(x) { if (!test1 (x)) { return
| false; } if (!test2 (x)) { return false; }
| if (!test3 (x)) { return false; } if (!test4
| (x)) { return false; } return true; }
|
| And it would be much safer from stupid copy/paste errors.
| jhgb wrote:
| Instead of function check(x) {
| if (!test1 (x)) return false; if (!test2
| (x)) return false; if (!test3 (x)) return
| false; if (!test4 (x)) return false;
| return true; }
|
| why wouldn't you do something along the lines of
| (every (lambda (f) (funcall f x)) (list #'test1 #'test2
| #'test3 #'test4a))
|
| or whatever is the equivalent in your preferred language?
| wvenable wrote:
| The tests are rarely that simple or regular.
| jhgb wrote:
| Isn't that exactly the reason to put them somewhere else?
| wvenable wrote:
| No, I just mean the actual tests might be something like:
| if (user == null) return false; if
| (IsCompleted(user)) return true; if (action ==
| null) return false; if (value < 0 || value > 100)
| return false;
|
| You're not usually just passing a single value to a bunch
| of "test" functions.
| brundolf wrote:
| Worth noting that elses don't require curly braces, which
| are where most of the visual noise is coming from in your
| example: function check(x) { if
| (!test1 (x)) return false; else if (!test2
| (x)) return false; else if (!test3 (x)) return
| false; else if (!test4 (x)) return false;
| else return true; }
|
| I think this is more clear than either of the above
| tpoacher wrote:
| > Worth noting that elses don't require curly braces
|
| True. I still think the first is clearer though. Either
| all tests pass, or they don't.
| tpoacher wrote:
| (also I'd be very skeptical of such an if..else ladder.
| Prime target for bugs.)
| cryptonector wrote:
| On the contrary, if early returns allow you to outdent a
| larger portion of code then it's quite a readability win.
| As long as all the early return cases are bite-sized and
| the last case long-ish, it's a readbility win.
| jbverschoor wrote:
| And certain languages allow:
|
| return nil if n.nil?
| wvenable wrote:
| For a single condition it doesn't matter much either way
| for readability. But once you have a number of conditions
| the if/else style becomes unwieldy.
|
| I put all my early returns at the top as these are the
| preconditions for the rest of the code; I don't think
| that's hard to understand.
| kbenson wrote:
| I think your version isn't really as different as it seems
| to some. To me, the essential bit of early return is that
| you handle cases that eliminate the work first. That is,
| it's less about singular if statements separating the rest
| of the body of the function than it is clearly short
| circuiting the code as early as is clearly possible.
|
| That is, I don't think it matters too much whether you do:
| function div(x,y) { if (y == null) {
| return null; } if (y == 0) {
| return null; } return x/y;
| }
|
| or function div(x,y) { if (y ==
| null) { return null; } else if (y ==
| 0) { return null; } else {
| return x/y; } }
|
| as they both accomplish the same major benefit of the
| pattern.
|
| The real thing it's helping to avoid is accidentally
| creating something like this: function
| div(x,y) { if (y != null) { if (y !=
| 0) { return x/y; } else {
| return null; } } else {
| return null; } }
|
| which can quickly get very hard to reason about without in
| more complex cases.
| brundolf wrote:
| You're right that both technically qualify as early-
| return, which is why I qualified my original statement
| with "if you don't put the rest of the function in an
| else clause".
|
| In practice, in C-like languages, I tend to see the
| "else-less" version which is why I brought it up
| t-writescode wrote:
| I think the bigger problem you may be experiencing, if I
| had to guess, code structure.
|
| If people tend to write: function foo(x) {
| if (x == null) { return null; }
| // do some stuff to the code for(a
| reason) { // do some more stuff
| if(some detailed reason) { return null;
| } // more things return 7; }
|
| Then the shape of the code and the "pattern" of early
| returns gets broken.
|
| If you read code where all of the short-circuit, early-
| return logic is at the start of the function, and then all
| of the work is done, do you still have this opinion?
|
| E.g. function foo(x) { if(x ==
| 1) { return null; } if(x == 2) { return null; }
| if(x > 7) { return null; } // do things with
| x return 7; } ?
| brundolf wrote:
| The problem is definitely much worse when returns are
| scattered throughout the function instead of grouped at
| the top, but I still prefer them to be "elsed" together
| (or in a switch/cond/match statement or whatever). Worth
| noting that in Lisp - one of the classical languages
| where early-returning is popular - there isn't a problem
| because it is done as I describe: (cond
| (early-return-condition early-value) (second-
| early-condition early-value2) ... (t
| final-else-value))
|
| The "default case" is on the same footing as the other
| cases. Only in procedural languages does the control-flow
| get confusing if you aren't careful.
| t-writescode wrote:
| If you want elses everywhere, how do you protect yourself
| from nesting hell? I find nesting hell to be far, far,
| faaaar worse for readability than any of the
| alternatives, and will move to the early return model to
| escape all that nesting.
| brundolf wrote:
| I dunno, I just rarely ever find myself nesting more than
| two or three levels of conditionals even in extreme
| cases. If my logic really is that complicated, it's
| probably a sign it needs to be broken up anyway. And if I
| did need that much branching, I would want to be _more_
| explicit about it, not less.
|
| Also - at the very least - you can mimick the "flat"
| early-return style and just stick some else's in there
| and call it a day. That's my main point; less so the
| actual deeper nesting
| ufo wrote:
| I have the same opinion. I like the early-return pattern
| when it's doing input validation or raising errors but for
| most other things I prefer how the if-else highlights that
| there are two possible paths that the code can take. Just
| because the "then" branch has a return statement doesn't
| mean that we should get rid of the "else".
| jbverschoor wrote:
| Sadly, a lot of junior and medior programmers see that as a
| wrong pattern, and opt for a return variable.. Drives me
| nuts.
| vsareto wrote:
| They should really use both
| speedyapoc wrote:
| I recall this pattern being taught in my first year or two
| of my computer science degree. Emphasizing only one return
| statement per function, utilizing a shared return variable.
|
| I much preferred (and still greatly prefer in industry) the
| pattern of exiting the function as soon as possible.
| jbverschoor wrote:
| Same here.
|
| Returning and throwing quickly and strict.
| cryptonector wrote:
| It's like "goto considered harmless" -- structured code
| to max, to the point of unreadability. Dogmatism has
| drawbacks.
| joshAg wrote:
| In C when you have nothing that needs cleanup, it's not the
| most horrible thing ever: int foo(thing_t
| \*work) { int ret = E_SUCCESS;
| if(work->thing == BAD_THING) { ret = E_BAD;
| goto exit; } do_some(work);
| if(work->thing2 == OTHER_BAD_THING) {
| LOG("whoopsies"); ret = E_OTHER; goto
| exit; } ret = do_more(work);
| if (!ret) { LOG("lazy"); goto exit;
| } ret = last_bit_of(work);
| exit: return ret; }
| eh9 wrote:
| I would usually agree, but the suggested code would allow for
| multiple errors to be shown before they're thrown.
| high_byte wrote:
| switch true is a cool new trick to me, but early return is the
| way to go.
| 1_player wrote:
| Early return is my silent gauge to tell if a piece of code has
| been written by a junior or a senior software engineer.
|
| I still see far to many snippets with multiple levels of
| indentation, where each if branch is for the happy path, and if
| they were converted to early returns you could flatten the
| whole thing to 1 indentation level, at the expense of requiring
| negated boolean expressions which aren't as readable.
| qudat wrote:
| Agreed! I have three rules for writing functions:
|
| - Return early
|
| - Return often
|
| - Reduce levels of nesting
|
| https://erock.io/2019/07/11/three-rules-for-refactoring-func...
| kvczor wrote:
| Linting got messed up in the comment above, but in the actual
| code this is nicely readable.
| [deleted]
| tengbretson wrote:
| I've grown to dislike the early return pattern. It's pitched as
| a way to reduce visual complexity by reducing levels of
| indentation, but I don't actually find that to be a benefit in
| most cases. Reducing indentation is just a trick to shove more
| cyclomatic complexity into a function without it triggering
| your sensibilities to implement proper abstractions.
| mwkaufma wrote:
| 1000X this.
| elyseum wrote:
| No need to 'return' when you throw an error, but your approach
| is valid IMO: if structure / readability is that important,
| refactor the switch to it's own method with only if checks in
| it. Hard to make that simpler and more readable.
| notyourday wrote:
| If you don't have a 'return' there's nothing to say that some
| time later some junior developer would not modify throw to be
| something else or forget that throw does not return.
| BiteCode_dev wrote:
| That's what tests are for.
| dfee wrote:
| This will prevent the JR dev from making that mistake:
| https://eslint.org/docs/rules/no-fallthrough
| 0xcoffee wrote:
| I've also seen code written as: _ =
| !isDefined(user) && throw new Error("User must be defined.");
| _ = !isString(user.firstName) && throw new Error("User's first
| name must be a string");
|
| But I while it is concise, I can also understand why people
| prefer regular if statements.
|
| Doesn't seem to be valid JS, can't remember where I got it from
| IggleSniggle wrote:
| My preference for this is to use an assertion function, eg in
| nodejs:
|
| assert(isDefined(user),"User must be defined") // throws if
| Falsy
| CloselyChunky wrote:
| In my opinion this pattern is better if you write it like
| this: _ = isDefined(user) || throw new
| Error("user must be defined")
|
| This reads way more natural for me. "A user is defined OR
| throw an error"...
|
| I've also seen this in Perl (`do_something() || die()`) and
| shell scripts (`grep -q || die "not found"`).
| pxx wrote:
| In perl you would want to use `or` instead of `||` to take
| advantage of the low precedence, so you can type things
| like `dostuff $foo or die` which is logically 'do stuff,
| and if it fails, die' as opposed to 'die if $foo is false',
| which you'd get with ||
| lhorie wrote:
| It's a stage 2 proposal, which technically can be used today
| via babel[0] (though personally, I don't recommend using
| stuff below stage 4)
|
| [0] https://babeljs.io/docs/en/babel-plugin-proposal-throw-
| expre...
| d1sxeyes wrote:
| This pattern is quite common in React to do conditional
| rendering: {isLoading && <Loading />}
| qalmakka wrote:
| So that has got a name? I always instinctively wrote code like
| that, because it's much more easier to understand.
| aastronaut wrote:
| This style gets the label 'poor-mans-pattern-matching' from me.
| If pattern matching would not be in my daily vocabulary, as it's
| also not available in JS, I'd consider it a misuse of switch/case
| and this post also makes an odd example for its usefulness.
|
| The example I would pick is the following: Consider you need to
| switch depending on a version (of a specification in my case),
| but this version isn't represented as an enum in the codebase,
| but as a number instead. So our team had something like this in
| the codebase (early return): function
| foobar(version: number): string { if (version === 3.1 ||
| version === 3) { return 'result_3'; }
| if (version < 2 && version >= 1) { return 'result_1';
| } if (version >= 2) { return 'result_2';
| } throw new Error(`Cannot interpret version
| '${version}'`); }
|
| I read it as "people don't care about branching order that much,
| so how can I make my wish for better readability more clear?"....
| my end goal then was to bring it into this state (a distinct enum
| as discrete value of the version): enum Version {
| _1_1 = 1.1, _2 = 2, _3 = 3, _3_1 =
| 3.1, }; function foobar(version: Version):
| string { switch (version) { case Version._3_1:
| case Version._3: return 'result_3'; case
| Version._2: return 'result_2'; case
| Version._1_1: return 'result_1'; default:
| (function (val: never): never { throw new
| Error(`Exhaustiveness reached: ${val}`); })(version);
| } }
|
| ...and my interim solution that made it into the PR in time
| turned out to be something like this (switch true):
| function foobar(version: number): string { switch (true)
| { case version >= 3: return 'result_3';
| case version >= 2: return 'result_2'; case
| version >= 1: return 'result_1'; default:
| throw new Error(`Cannot interpret version '${version}'`);
| } }
|
| My PR was flagged by the team for misuse of the switch statement,
| we had some discussion and I changed it back to the simple
| if/else branching from above.
| alerighi wrote:
| switch (Math.floor(version)) { case 1:
| return 'result_1'; case 2: return
| 'result_2'; case 3: return 'result_3';
| default: throw new Error('...'); }
|
| Isn't that more clear?
| mwkaufma wrote:
| Even accepting the dubious premise that "pretty text =
| maintainable code", he's juked his exampled by (i) littering the
| simple early-outs with unnecessary "else"s and (ii) stripping the
| "break"s from his switch.
| brundolf wrote:
| It's interesting, but I can't decide if it's an anti-pattern or
| not. You're abusing a construct to achieve a slight improvement
| in brevity/readability, with the downside of JS's lack of block-
| scoping for case statements which means variables in one case can
| conflict with variables in other cases
|
| All in all: I probably won't be using it
| jypepin wrote:
| I remember when I first discovered the concept of pattern
| matching, I tried to find a way to "hack" the switch statement in
| js to make it work like pattern matching.
|
| The switch(true) format was the closest I got to it, which I
| personally don't like compared to a clean if/else or early
| return.
|
| There's probably some performance differences between if/else and
| switch (I haven't checked) but it probably end up being what's
| your preference / what standard code style you want to enforce on
| your team.
| scotttrinh wrote:
| Hopefully we will get real pattern-matching at some point
| (https://github.com/tc39/proposal-pattern-matching), but I kinda
| sorta like this!
| [deleted]
| [deleted]
| vikingcaffiene wrote:
| I'd flag this if it came up in PR. It's clever and novel which is
| precisely why it's a bad idea. Always bet on boring.
| xwolfi wrote:
| Exactly, because it won't be the first innovation nor the last
| and if you let them all through, your code becomes a bazaar of
| curiosities rather than a business solution...
| hatch_q wrote:
| Same. I also flag seemingly good things like !!x. If you want
| to convert something to boolean be explicit and do Boolean(x).
| In most cases it turns out there was a bug in input parameter
| in the first place and developer was just lazy to fix it.
| encoderer wrote:
| For many years I stopped using this after getting flack in pull
| requests.
|
| I've recently added it back into my toolkit and am reminded how
| much I love it. Don't over use it but there are some really
| gnarly if/else blocks that can be expressed beautifully with a
| switch and fall-thru.
| jbverschoor wrote:
| If (x) throw y;
|
| If (s) throw t;
|
| Throw default;
| jimjimjimjim wrote:
| I think the beauty of the switch statement is that when you see
| one you know you're just concentrating on the value of one
| variable. I think the if else if is actually cleaner for the user
| example in the post.
| borishn wrote:
| The article is misleading, in implying that `switch(true)` is a
| special case: "The fundamental principle of the switch true
| pattern is that you can match against expressions as well as
| values."
|
| It should be states as "The fundamental principle of the switch
| pattern in JavaScript is that you can match against expressions
| as well as values."
|
| From https://developer.mozilla.org/en-
| US/docs/Web/JavaScript/Refe...:
|
| A switch statement first evaluates its expression. It then looks
| for the first case clause whose expression evaluates to the same
| value as the result of the input expression
| 1_player wrote:
| That's good and dandy until one changes one case block to normal
| statements instead of a terminating one, forgets to add a
| "break;" and someone has a nightmare debugging session trying to
| figure what is going on.
|
| Go did good by making case blocks break automatically and
| requiring the "fallthrough" keyword in one of those very rare
| cases you need it do.
| jacknews wrote:
| Is this a real thing? It looks incredibly hacky to me. What
| happens when multiple cases are true, are they all handled? In
| what order? What happens if one of them returns? Etc.
| Kinrany wrote:
| The same way switch works in every language and regardless of
| the clever pattern: find the first expression that equals and
| jump to that label.
| int_19h wrote:
| A better question is: which "case" expressions are evaluated
| - all of them, or only the ones enumerated before the match
| was found?
|
| Unless you use this pattern regularly - and most coders
| don't, even those who mostly write JS - you'll probably have
| to look the answer up. That alone is a good reason to stick
| to if/else; there's no such ambiguity there.
| lifthrasiir wrote:
| > A better question is: which "case" expressions are
| evaluated - all of them, or only the ones enumerated before
| the match was found?
|
| Good point. By the way the answer is the latter, so the
| following prints 1 and 2. switch (true) {
| case (console.log(1), false): case
| (console.log(2), true): case (console.log(3),
| false): break; }
| tsimionescu wrote:
| Many languages don't allow duplicate values for case in
| switch statements, so that question never comes up - this is
| true for C, C++, C#, Java. Go, Ruby do behave like JS here.
| mlyle wrote:
| But in many languages, the cases need to be compile-time
| constant rvalues.
| erikerikson wrote:
| Validation code as shown tends to be repetitive and imperfectly
| implemented. I have found that transitioning to using AJV and
| JSON Schema is far more sustainable, especially on an API
| surface. One describes the data and depends on consistent and
| vetted logic for validating the described types rather than
| repetitively describing how to validate them.
|
| Validations that happened at an application level must still be
| written but those tend to be specific to the application logic or
| system state. An example of logic related validation is
| contingently valid argument values where the compatibility of one
| value being used with another must be tested. An example of state
| related validation is a constraint that a given value must exist
| in a dynamic table.
| crishoj wrote:
| While readable and aesthetically pleasing, I find myself
| wondering about the performance implications of switch(true)
| versus a multi-branched if-else. Does V8 (and PHP) treat each
| construct differently when it comes to optimizations? We're not
| in C-land here, so jump tables are presumably not in play.
| [deleted]
| jbverschoor wrote:
| The example shouldn't be compared to esleif, that's not how
| switches work.
|
| Also, because it throws you can just use if, without a block. Or
| use if at the end of the line if your language supports that.
|
| Way cleaner (less indentation), and less error prone. Switch
| statements only exist because of the underlying assembly/opcode.
|
| It just as bad as goto, because it IS goto. The cases are goto-
| labels. It behaves like goto, and will simply generate the same
| JE/JNE jumps
___________________________________________________________________
(page generated 2021-04-12 23:01 UTC)