C++17 has a comment-like language feature for demarcating desired fallthrough.
Fallthrough in case processing is often useful; it's just not such a great default.
In the TXR Lisp tree-case[2] construct, any case which returns the : object falls through to the next case.
This is used all over the place in the library.
--
[2] https://gcc.gnu.org/onlinedocs/gcc-9.3.0/gcc/Warning-Options...
This is a tangent, but it reminds me of a personal policy of mine: if I'm thinking about changing a default, I ask myself if there should be a default at all.
The consequences of having the wrong default were serious enough that it even came to my attention. So by changing the default, am I potentially exchanging one set of problems for another?
Sometimes it would be better overall if the user is forced to explicitly state which way they want it. Not always, of course, but I like to ask the question.
It’s not clear to me that an “order by” should have a default direction. I don’t know why one direction is more likely to be used than the other. Without a default, I would have been forced to think about the direction and wouldn’t have had that bug.
You can read the 1999 Perl Conference paper for the perl script at https://sites.google.com/site/typopl2/afastandeasywaytofindb...
I feel relatable. At all the placed I've worked, people realize the problem, sometime find the solution, but most of the time, they won't fix it, unless it directly affect their code/project/product. As a result, technical debt keeps bubble up and we have more and more issues.
Right now, the only thing I can do is just to keep fixing issues as I encounter them. But that's for the code, I don't have any solution for the "people" problem.
Going rogue and spending an afternoon fixing stuff without an approved US (to charge time on) was not seen well.
I’m talking from personal experience of going rogue :)
https://devblogs.microsoft.com/oldnewthing/20110118-00/?p=11...
For whatever reason that heavily influenced me, and now I almost always submit patches together with my bug reports. Sometimes its a whole lot more work, but I feel like this practice pays dividends in the amount you learn as well as the comraderie it fosters with the upstream devs.
`continue` is a thing, compliments switch's existing `break` statement usage and is overall a much better solution for explicitly specifying fallthrough then returning special objects or c#'s abuse of goto.
I mean you could kinda argue that switch could conceptually mean something like
for matcher, block in cases:
if matcher.matches(subject) or continued:
eval block
but then continue still feels magic since it bypasses the test.As a language-design aside, `switch` in general is stinky stuff. Not just with the fall-through: it also violates the regular C-style syntax for no particular reason, having a mini-syntax of its own instead.
But the most perverse thing I've seen done with `switch` is using it as `if`:
switch (true) {
case ($a == $b): ...
case ($c == $d): ...
case (itsFullMoonToday()): ...
default: ...
} register n = (count + 7) / 8; /* count > 0 assumed */
switch (count % 8)
{
case 0: do { *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while (--n > 0);
} switch (foo)
{
case 1:
...code just for case 1...
if (0)
{
case 2:
...code just for case 2...
}
...code for both case 1 and case 2...
break;
case 3:
...
}
used when two cases have a common tail, and you have sufficient space constraints that you do not want to duplicate the common tail code, and you have sufficient time constraints (and maybe space constraints on the stack) that you don't want to put the common tail code in a subroutine and call it from both cases.One of the nice advantages we had in using our static analysis tool for this, with real code intelligence, instead of a simpler linter, was that we could do a much more clever check. The actual rule was "every case must be terminal", and we had a rich analysis for "terminal", so stuff like this would be considered legal:
case blah:
if (cond) {
throw blah;
}
if (cond2) {
some_noreturn_function();
} else {
return 42;
}
etc etc. My example is a bit contrived, and the wisdom of doing such complicated things in switch/case is questionable, but it was useful when dealing with an existing codebase.Another advantage was that our tool was designed for extremely fast analysis over the entire codebase, so errors were given to programmers as they were writing code, immediately. (200ms response time to any change. At that level you can run it on every keystroke, which we did.) Traditional linters can in principle do this, but in practise are often not written with this sort of performance requirements in mind.
(And yes, PHP allowing arbitrary expressions in case labels was... probably not a good idea.)
> PHP allowing arbitrary expressions in case labels was... probably not a good idea.
Yeah, possibly the worst offender is the (somewhat common) "switch (true)" pattern: https://psalm.dev/r/4c168a9c5d
Sadly I don't have the latitude you all do to govern how code gets written by users of my own static analysis tool – fall-through has to work there, too: https://psalm.dev/r/1d4ee59bd6
Most people treat switch as a peculiar form of match (or cond or whatever), probably because this would usually be much more useful to have than a computed goto, and in many derivative languages they've put in weird additions that seem to be an attempt to drag it halfway to that, things that don't really make any sense in C, like allowing dynamic expressions in the cases (such as your example), or forcing all cases to be at the top level.
(I feel like I write a lot of "in defense of switch" comments on HN...)
Completely made up switch illustrates how I use it in every language if I can...Go has a succinct version of this.
switch(True) {
case ($x == 1):
$x++;
break;
case ($x > 3):
$x--;
break;
default:
break;
}I quite like it, as it cuts down on the amount of brace and if/else if noise.
Now, grinding everyone's gears with his abuse of `switch`—that was right up his alley, what with already grinding them with his political commentary outside of code.
but in a very high-level language like PHP, there’s really no reason for switch to fall through at all
I disagree. Switch fallthrough is very useful because it can reduce code duplication, especially when the logic has a ladder-like structure. Trying to impose increasingly draconian and such arbitrary rules is only going to lead to a self-fulfilling-prophecy where all the intelligent developers will get fed up and leave, and what's left are those which will continue to create tons of bugs some other way instead.
Yeah, you can't just mechanically insert the breaks :) I carefully went through each instance where my rule tripped, looked at the surrounding code, and figured out the right fix. Trying to automate that would indeed have lead to disaster -- and the lack of ability to automate this is why no one had done this in the past, it sounded like too much work. (It wasn't that much work.)
And I agree switch fallthrough can be useful! We just required it be annotated from now on, to convey the intent, as opposed to doing it by accident.
To me, this is the same attitude that leads to safety hazards in other industries. "I don't need safety measures because -I- am not an idiot".
Even the smartest developers make dumb mistakes. If you can eliminate some of those early with minimal friction, it's worth it. The earlier you catch a mistake, the cheaper it is. Linter rules are less of a hassle than bugs in production.
switch ($foo) {
case 123:
// do 1
case 23:
// do 2
default:
// do 3
}
if ($foo == 123) {
do1();
do2();
do3();
}
else if ($foo == 23) {
do2();
do3();
}
else {
do3();
}
Yes, the second one is more verbose and duplicates code, but I think it's also far more clear in terms of intention. if ($foo == 123) {
do1();
}
if ($foo == 123 || $foo == 23) {
do2();
}
do3();http://neugierig.org/software/chromium/notes/2011/01/clang.h...
switch (c) {
case 4: return 30;
case 5: return 70;
default: return 0;
}
compiles to the same instructions and therefore same performance as if (c == 4) {
return 30;
}
else if (c == 5) {
return 70;
}
else {
return 0;
}
As a side note, it's easier to see accidental fallthroughs if you write switch statements like this. switch (c) {
case 4: {
return 30;
} break;
case 5: {
return 70;
} break;
default: {
return 0;
} break;
But in my opinion, the `if` statement is more clear in both cases, and only one level of indentation instead of 2.Eye of the beholder, indeed.
if (c == 4) return 30;
if (c == 5) return 70;
return 0;Even if the cases are consecutive, most compiler optimizers are smart enough to determine that the ASTs are equivalent.
I joked that we'd make a JIRA ticket that we'd repeatedly deprioritize until our code mysteriously broke in 6 months, but then my coworker actually made an Outlook reminder for when we had a month left!
I thought that was really smart, so I guess I learned a lesson about actually solving problems instead of just pointing them out like a smart ass.
The typical way to handle this at companies that don't have a static-type-checker department is to require a comment that says /* falls through */ at the end of the switch. Why was such a simple solution inappropriate here?
Sorry about your last company, though. Sounds like it was annoying to work there!
The cleanup diffs I’d written that afternoon mostly just annotated clearly-intentional fallthroughs
- Linting files different from origin/master in a pre-commit hook.
- Linting everything in CI.
- Making the linter rules stricter (mypy does a great job of having several orthogonal strictness flags, so you can pick the set appropriate for your familiarity with the tool).
- Implementing project-specific lint checks.
I use Swift. It does not have default switch fallthrough. After a couple of versions, I learned about the fallthrough statement.
I remember being frustrated by it, at first, but, like so many Swift oddities, it rapidly became second nature; thus, proving out that one of the goals of Swift is to train developers to write code properly.
Swift is a deep river. I still learn new stuff every day, and I write Swift, seven days a week.