switch()

Andrei Alexandrescu SeeWebsiteForEmail at erdani.org
Sun Feb 16 22:18:45 PST 2014


On 2/16/14, 7:42 AM, Manu wrote:
> So D offers great improvements to switch(), but there are a few small
> things I wonder about.

TL;DR of my answer to this: at some point we must get used to the notion 
that minute syntax tweaks are always possible that make us feel we're 
making progress when instead we're just moving the rubble around.

> 1.
> case fall-through is not supported; explicit 'goto case n;' is required.
> With this in mind, 'break' is unnecessary. Why is it required? It could
> be implicit upon reaching the next case label, or a scope could be used
> (with support for omitting the scope for single statements as with if).
> It's really noisy, and annoying to write everywhere.

Implicit fall-through has been specifically eliminated from the language 
at my behest. I think it is a fine language change. Use "goto case;" to 
clarify to the maintainer (and incidentally the compiler) you want a 
fall-through.

> 2.
> 'case 1, 3, 7, 8:' is awesome! ...but ranged cases have a totally
> different syntax: 'case 1: .. case 3:'
>
> Why settle on that syntax? The inconsistency looks kinda silly when they
> appear together in code.
> Surely it's possible to find a syntax that works without repeating case
> and ':'?

There's no inconsistency. The case is not identical with "foreach (a .. 
b)" or arr[a .. b], both of which don't include b in the explored range. 
The fact that "case b:" is present is very telling "b" will be acted upon.

> It's also weird, because it seems that 'case n: .. case m:' is inclusive
> of m. This may be unexpected.

It's expected.

> I'm not sure it's reasonable to use the '..' syntax in this case for
> that reason. '..' is an [) range, case ranges must be [] so that it
> makes sense when dealing with enum key ranges.

No.

> 3.
> Why is 'default' necessary? If I'm not switching on an enumerated type,
> then many values are meaningless. requiring an empty 'default: break;'
> line at the end is annoying and noisy.

Explicit is better than implicit.

> I often find myself tempted to rewrite blocks of successive if() logic
> comparing integers against values/ranges, but it looks silly since the
> scope rules are not explicit, and 'default: break;' always wastes an
> extra line.

Write the line.

> I like to reduce noise in my code, and these switch semantics threaten
> to simplify a lot of code, if not for these strange decisions (purely
> for legacy compliance?).

I think the current switch statement design is a fine design all things 
considered (compatibility, law of least surprise, usability, readability).

> Let's consider an example:
>
> Code like this:
>
> int difficulty = -1;
> if(e.note.note >= 60 && e.note.note < 72)
> difficulty = 0;
> else if(e.note.note >= 72 && e.note.note < 84)
> difficulty = 1;
> else if(e.note.note >= 84 && e.note.note < 96)
> difficulty = 2;
> else if(e.note.note >= 96 && e.note.note < 108)
> difficulty = 3;
>
> The repetition of e.note.note is annoying, and particular choice of
> comparisons are liable to result in out-by-ones. It's not nice code to read.

For every proposed tweak there will be an example that makes it look great.

> Rewrites like this:
>
> int difficulty;
> switch(e.note.note)
> {
> case 60: .. case 71:
> difficulty = 0;
> break;
> case 72: .. case 83:
> difficulty = 1;
> break;
> case 84: .. case 95:
> difficulty = 2;
> break;
> case 96: .. case 107:
> difficulty = 3;
> break;
> default:
> difficulty = -1;
> break;
> }
>
> That's horrid, it's much longer! And there are pointless wasted lines
> everywhere.
> The default case is a total waste, since -1 should just be assigned when
> initialising the variable above.

But you'd be wasting an extra assignment. I recall you're one for 
efficiency.

> We can compact it a bit like this:
>
> int difficulty;
> switch(e.note.note)
> {
> case 60: .. case 71:
> difficulty = 0; break;
> case 72: .. case 83:
> difficulty = 1; break;
> case 84: .. case 95:
> difficulty = 2; break;
> case 96: .. case 107:
> difficulty = 3; break;
> default:
> difficulty = -1; break;
> }
>
> But that's horrible too.

The quality of being horrible is in the eye of the beholder. I find this 
code entirely reasonable.

> It's not clear what vertical offset the 'break'
> statements shoudl appear at (I hate stacking up multiple statements
> across the same line!).
> The the default case is still a waste.

Just do it.

> Ideally:
>
> int difficulty = -1;
> switch(e.note.note)
> {
> case 60 .. 72:
> difficulty = 0;
> case 72 .. 84:
> difficulty = 1;
> case 84 .. 96:
> difficulty = 2;
> case 96 .. 108:
> difficulty = 3;
> }

Nope.

> 'break's are unnecessary since fallthrough isn't allowed.

Silently change the semantics of C code is not something we entertain doing.

> Proper numeric range could be supported (assuming [) intervals).
> 'default' case is unnecessary, and removed.
>
> The switch and braces results in 3 extra lines, but I still feel this
> level of simplification results in code that is MUCH more readable than
> the sequence of if's I started with. It's super obvious what's going on.
>
> I have quite many blocks like this.
>
> A great man once (actually, frequently) said "If it doesn't look right,
> it probably isn't".

The thing is you can apply that to everything, and justify every tweak, 
because what looks right is subjective.


Andrei



More information about the Digitalmars-d mailing list