Thread overview
Re: switch()
Feb 16, 2014
Manu
Feb 16, 2014
Andrej Mitrovic
Feb 16, 2014
Andrej Mitrovic
Feb 17, 2014
Jonathan M Davis
Feb 21, 2014
Andrej Mitrovic
February 16, 2014
Another thing that I often do is switch on an expression, but I often have
a problem where, within the scope, I have no way to refer to the result
value.
This is particularly important when range cases appear.

switch(x+10)
{
  case 10: .. case 20:
    // what is the value?
}

Obviously then I need to do this:

auto y = x+10;
switch(y)
{
  case 10: .. case 20:
    // I know y...
}

But this is a bit lame. I'm polluting the outer namespace, and wasting a line.

I wonder if a variable declaration could be made possible in the switch:

switch(y = x+10) // obviously implicitly auto, like in foreach
{
  case 10: .. case 20:
    // I have y, no pollution of the outer scope, no wasted line. yay!
}

for and foreach can both declare variables this way... does it make sense here? Useful?



On 17 February 2014 01:42, Manu <turkeyman@gmail.com> wrote:

> So D offers great improvements to switch(), but there are a few small
> things I wonder about.
>
> 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.
>
> 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 ':'?
>
> It's also weird, because it seems that 'case n: .. case m:' is inclusive
> of m. This may be unexpected.
> 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.
>
> 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.
>
> 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.
> 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?).
>
>
> 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.
>
>
> 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.
>
>
> 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. 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.
>
>
> 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;
> }
>
> 'break's are unnecessary since fallthrough isn't allowed. 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".
>


February 16, 2014
On 2/16/14, Manu <turkeyman@gmail.com> wrote:
> Another thing that I often do is switch on an expression, but I often have a problem where, within the scope, I have no way to refer to the result value.

You're not the only one with that problem: https://d.puremagic.com/issues/show_bug.cgi?id=11070
February 16, 2014
On 2/16/14, Manu <turkeyman@gmail.com> wrote:
> case fall-through is not supported; explicit 'goto case n;' is required.

You can actually use "goto case;" now, without specifying the actual case.

> '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
> ':'?

I've tried asking for it: https://d.puremagic.com/issues/show_bug.cgi?id=11213

> 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.

One reason is to allow runtime diagnostics or exceptions being thrown, e.g.:

switch (val)
{
    case "foo": ...
    case "bar": ...
    default: assert(0, format("Unhandled case: %s", val));
}
February 16, 2014
On Sun, 16 Feb 2014 11:08:51 -0500, Manu <turkeyman@gmail.com> wrote:

> Another thing that I often do is switch on an expression, but I often have
> a problem where, within the scope, I have no way to refer to the result
> value.
> This is particularly important when range cases appear.
>
> switch(x+10)
> {
>   case 10: .. case 20:
>     // what is the value?
> }
>
> Obviously then I need to do this:
>
> auto y = x+10;
> switch(y)
> {
>   case 10: .. case 20:
>     // I know y...
> }
>
> But this is a bit lame. I'm polluting the outer namespace, and wasting a
> line.
>
> I wonder if a variable declaration could be made possible in the switch:
>
> switch(y = x+10) // obviously implicitly auto, like in foreach
> {
>   case 10: .. case 20:
>     // I have y, no pollution of the outer scope, no wasted line. yay!
> }
>
> for and foreach can both declare variables this way... does it make sense
> here? Useful?

Yes, this would be good. I bet bearophile has an enhancement request already ;)

-Steve
February 17, 2014
On Monday, February 17, 2014 01:42:59 Manu wrote:
> So D offers great improvements to switch(), but there are a few small
> things I wonder about.
> 
> 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.

That goes against the D philosophy of making it so that C code is either valid D code with the same semantics, or it doesn't compile, so it's arguably not an option, much as it would make switch statements much less noisy. We _have_ in a very few cases broken the rule about C compatibility (e.g. static arrays are passed by value rather than reference), but it's not something that we've done much. And in this case, it could be particular pernicious, because any C/C++ code being ported over would end up with the reverse of its current semantics in any case that doesn't end with break. The same goes for any older D code that hasn't been compiled since implicit fallthrough became illegal. Compiling it with the newer compiler which had implicit breaks would silently alter its semantics.

So, while in principle, I like the idea, I don't think that it's actually a viable one given D's design goals and history.

- Jonathan M Davis
February 21, 2014
On 2/16/14, Manu <turkeyman@gmail.com> wrote:
> requiring an empty 'default: break;' line at the end is annoying and noisy.

Guys, I keep seeing this line being mentioned but you don't actually have to type "break":

-----
void main()
{
    int x;
    switch (x)
    {
        case 1:
            break;

        // this works fine
        default:
    }
}
-----