7 ms·
Coded the "normal" way, you get this: if(githubUrl) { return config.github; } if(twitterUrl) { return config.twitter; } ret
by skrebbel 7y ago
Coded the "normal" way, you get this:
if(githubUrl) {
return config.github;
}
if(twitterUrl) {
return config.twitter;
}
return {
to: '',
...
};
IMO this is both clearer and easier on the eyes. Can anyone show me an example of this `switch` pattern that actually makes it better than the obvious if-based version?
I mean, given that they're early returns, you don't need any `else` statements. And in situations where you couldn't use early returns, the `switch` pattern would require `break;` statements. That would make that code uglier to the same extent as adding `else`s above. What am I missing?
- jbverschoor 7y agoYeah never really liked switches because of the "breaks". Switch statements are a more like goto, and are more low level. I tend to do: if (xx) { } else if (yy) { } else { // maybe some exception for unhandled value } And to align my ifs: if (false) { } else if (xx) { } else if (yy) { } else { // maybe some exception for unhandled value } But then team members and tooling start to complain about dead code.
- w0utert 7y agoThis style of writing switch statements as long sequences of if-elseif-else makes it very easy to mess up when you add additional clauses. It also does nothing to guarantee the conditions are mutually exclusive, which would make the comparisons order-dependent. Last but not least I would guess the compiler does not have as many options to optimize this kind of code, compared to switch statements (a switch statement can be compiled to either chained jumps, or a jump table, but the if-elseif-else implies there could be order-dependencies and/or side-effects evaluating the conditions), limiting the compiler. This is all reasoning from the perspective of a compiled language of course. But even in Javascript implementing switch-statements as if-elseif-else seems like an anti-pattern to me. >> Switch statements are a more like goto, and are more low level. I know about 'goto considered harmful', but sometimes a simple jump table is just what you need, no need to come up with 'better' ways ;-)
- jbverschoor 7y agoIf the code produces sideeffects, a switch statement is not even an option, as it can only compare values (at least in this form). If you're checking some status code, I still prefer ifs. The example in this post is simply misuse of a switch statement. Interestingly, this optimization is relatively new for LLVM: http://llvm.1065342.n5.nabble.com/llvm-dev-RFC-JumpMaps-switch-statement-optimization-td105986.html http://llvm.1065342.n5.nabble.com/llvm-dev-RFC-JumpMaps-swit... https://reviews.llvm.org/D32742 https://reviews.llvm.org/D32742
- w0utert 7y ago>> If the code produces sideeffects, a switch statement is not even an option, as it can only compare values (at least in this form) Yes, this is exactly one of the reasons to use switch statements instead of if-elseif-else, were possible. Even if in the absence of side effects it is still easy to have overlapping conditions in the if-elseif-else, for example misspelling an enumeration value, some copy/paste mistake, etc. A switch statement does not allow this. >> If you're checking some status code, I still prefer ifs My philosophy is always to write code that expresses as much as possible its intent, and the sequence of operations the computer has to take. Performing some action based on a status code, in my mind, is like looking a procedure by its label, and executing it. Not looking at all procedures one-by-one until you find one that 'advertises' it applies to your status code. To each their own, I guess ;-)
- jbverschoor 7y ago>> A switch statement does not allow this. True, but a compiler will detect unreachable code when you do: if (x) { else if (x) { // warning } >> My philosophy is always to write code that expresses as much as possible its intent agreed >>, and the sequence of operations the computer has to take. From a performance perspective yes, from a logic / maintanance perspective not really.
- w0utert 7y ago>> True, but a compiler will detect unreachable code when you do Interestingly, neither clang nor g++ give a warning for this case: int main(int argc, char **argv) { int x = argc; if (x == 0) { x = 10; } else if (x == 1) { x = 20; } else if (x == 0) { x = 30; } return 0; } # g++ bla.cpp -o out -Wall (no warning) # clang bla.cpp -o out -Wall (no warning) I assume this is because the compiler can never be 100% sure a variable cannot be changed midway through testing each of the conditions because of aliasing, so it won't throw a warning. In this particular example this obviously is not relevant, but I can imagine some pathological example where one of the tests has side effects that change the result of the later tests, and to avoid false positives the compiler simply never warns about unreachable code for these things. In a sense, this is exactly what I mean by writing code that matches exactly what you want to happen. If I write 'switch (x)', it's pretty clear the test is executed exactly once. If I write a sequence of if-elseif-elseif-else statements, the conditions are evaluated in sequence, which is not what I want.
- rhinoceraptor 7y agoAs others have said, this pattern is used for pattern matching when the language doesn't have specific constructs for it. When I use it, I will extract it into a separate function, and just return the needed condition. That way you don't need to worry about case fall through. function getCondition(param) { switch (true) { case ConditionOne(param): return 'condition one' case ConditionTwo(param): return 'condition two' case ConditionThree(param): return 'condition three' } }
- metalgearsolid 7y agoSwift's nil-coalescing operator is a great solution for this. return config.github ?? config.twitter ?? default
- degif 7y agoThe same is in JavaScript: return (twitterUrl && config.twitter) || (githubUrl && config.github) || default
- masklinn 7y agoSeems like an unnecessary mess when you could just use ternary expressions: return twitterUrl ? config.twitter : githubUrl ? config.github : default
- Y-bar 7y agoI envy you, I’d love to grok ternary expressions better but every time I just end up having to spend a long time unrolling the logic.
- lsaferite 7y agoAnd you just introduced a bug. You refactored the code and changed the logic without noticing.
- mschuetz 7y agoI find if preferable to switch all the time. Switch needs two indents and virtually always that extra break, which makes it more complex all around than a simple if.
- emilsedgh 7y agoEarly returns are underrated.
- skrebbel 7y agoThey sure are. I often extract logic into a function just so I can use early returns and have less local state. I mean, they're a recipe for disaster in C++, but I don't code much C++ anymore. In JS and similar-enough languages it's lovely.
- humanrebar 7y agoThey're fine in C++ given RAII guarantees. I feel safer using them in C++ than most other languages.
- falcrist 7y agoI typically code in c and c#, and have been told by older engineers that it's important to avoid early returns because they can be confusing, especially when skimming through the code. I kind of see what he's talking about. It seems like it's a little easier to reason with the code and easier to set breakpoints sometimes. All that's to say: I'm curious as to why early returns would be better in JS.
- hackinthebochs 7y ago>avoid early returns because they can be confusing It seems to me like the exact opposite is true. Early return conditions stand out in your code and so the structure matches the flow. Its much clearer than having error state weaved through the mainline behavior of the function. I'm pretty sure the pattern of having only one return is popular among older engineers because it makes it easier to use pre-condition/post-condition/invariant style reasoning which used to be popular. Outside of this scenario I see no reason to avoid early returns.
- Agentlien 7y agoWhat do you mean by "recipe for disaster"? I use them a lot in C++ and as long as you stick with RAII (which you really should), I don't see any language-specific problems with it.
- tumetab1 7y agoIn the end I think it's a option of style in which "if" and "switch" are neither the correct match. The goal is to define a value (a variable or return value) in which a value is always established and it's established through several checks (exclusive or not). In this case the goal is to the definitively define a config given a URL. The "switch" and a default condition which I think express better that a value must be defined. With "ifs" we can add more complex logic and avoid the problematic "breaks" but we loose that in your face "default" condition. In theory it would be better to have a different control structure like "pickJustOne": return pickJustOne(url) { githubUrl: config.github; twitterUrl: config.twitter; default: config.unknown; } "pickJustOne" would have a companion named "pickOneByOrder" that would work like an "if" where the order of evaluation would matter.
- deleted 7y ago[deleted]
- Razengan 7y agoEDIT: Oops, chalk this up as another case of reinventing the wheel. We already have all this in the form of the ?: ternary operator, as masklinn pointed out: https://news.ycombinator.com/item?id=20736714 https://news.ycombinator.com/item?id=20736714 ---- Inspired by this, I cooked up a crude imitation in (ugly/functional'ish?) Swift: let result = [(firstCheck, 1), (secondCheck, 2), (thirdCheck, 3)] .first { $0.0() }?.1 That iterates over an array of (() - > Bool, Value) tuples and returns the second element of the first tuple whose first element is a function that returns true, otherwise nil. The types of all values must be the same. Could shorten .first to .firstTrue or something. And a version that's closer to your idea: typealias BooleanFunction = () -> Bool typealias FunctionValuePair<ResultType> = (BooleanFunction, result: ResultType) func pickOne<ResultType> (_ pairs: FunctionValuePair<ResultType>...) -> ResultType? { pairs.first { function, result in function() == true }?.result } let result = pickOne( (firstCheck, "one"), (secondCheck, "two"), (thirdCheck, "three")) ?? "default" You could make the default result an argument for pickOne so that it never returns nil, but I prefer to keep it an optional. Though it's too early in my day to think of better names. :)
- env123 7y agoI think the `switch` pattern is only applicable when you're checking for a large number of configurations that are of boolean type - ie. isReadOnly, isAdmin, isUser, isSuperUser, hasPaid, isActivated, isBanned, etc - it basically depends on your use case, unusual pattern to use, but it has its merits if applied in the right time by the right developer case isReadOnly: // change something; case isAdmin: doAdminStuff(); case hasPaid: updateFinance(); case isActivated: updatedUserInfo(); and so on...
- Zenst 7y agoVery true, then somebody else ends up maintaining the code and gets a ticket to add this feature and chaos plays out. One thing I learned early on - true and false vary across platforms. Hence I learned to define true and false at the start of the code with realTrue=(1=1) and realFalse=(1=0). As some systems have 1 or any positive value as true, some have -1 as true. It can get messy, bit like endians (big or little) the same holds true for true and false.
- VBprogrammer 7y ago> Hence I learned to define true and false at the start of the code with realTrue=(1=1) and realFalse=(1=0) I'm sure the platforms / language you use make this a sensible choice but I'm not going to lie, if I came across that in someones code I'd be screaming WTF at the top of my lungs.
- Zenst 7y agoI learned to code in the 70's, predating C99 and having to embrace many diverse platforms (we had more than one mainframe maker back then ;). But nice explanations here: https://stackoverflow.com/questions/17010041/why-define-true-1-1-in-a-c-boolean-macro-instead-of-simply-as-1 https://stackoverflow.com/questions/17010041/why-define-true... TL;DR portability