February 16, 2014
On Sun, 16 Feb 2014 11:37:48 -0500, Mike Parker <aldacron@gmail.com> wrote:

> On 2/17/2014 12:42 AM, 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.
>
> I only get the error when falling through to default, in which case it says:
>
>   Error: switch case fallthrough - use 'goto default;' if intended
>
> Fall-through compiles and runs otherwise. This is something I'm doing right now:
>
> switch( event.type ) {
>      // No error on falling through here <<<---------------------.
>      case SDL_KEYDOWN:
>      case SDL_KEYUP:

This is allowed, because there is no code between the case statements. It's the one case where fallthrough is allowed. If you put a statement in between the two, it would complain.

-Steve
February 17, 2014
What about new evolved switch statement, called something as to not confuse it with C syntax? It could be a simple rewrite thing.

mysteryswitch (some expression) {
  case 1: some statement;
  case 2 .. 4: some other statement;
}

could rewrite to

switch (some expression) {
  case 1: some statement; break;
  case 2: .. case 4: some other statement; break;
  default: break;
}
February 17, 2014
On Monday, 17 February 2014 at 00:22:52 UTC, Casper Færgemand wrote:
> What about new evolved switch statement, called something as to not confuse it with C syntax? It could be a simple rewrite thing.
>
> mysteryswitch (some expression) {
>   case 1: some statement;
>   case 2 .. 4: some other statement;
> }
>
> could rewrite to
>
> switch (some expression) {
>   case 1: some statement; break;
>   case 2: .. case 4: some other statement; break;
>   default: break;
> }

I like 'match' as Rust has:
----
match (some expression) {
    case 1: some statement;
    case 2 .. 4: some other statement;
}
----

February 17, 2014
On 2/17/2014 5:57 AM, Steven Schveighoffer wrote:

>
> This is allowed, because there is no code between the case statements.
> It's the one case where fallthrough is allowed. If you put a statement
> in between the two, it would complain.
>

That hit me this morning when I woke up. It's the reason I got the error when I fell through to default.

February 17, 2014
On 17 February 2014 06:03, Walter Bright <newshound2@digitalmars.com> wrote:

> On 2/16/2014 7:42 AM, Manu wrote:
>
>> 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's that way to prevent confusion from people used to C/C++, and/or transliterating code from such to D.


I figured that, but deliberately gimping a construct that could be far more useful than it is, just because it was crap in some other language seems like a poor choice to me.

 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 ':'?
>>
>
> Many syntaxes were proposed for that, and this was the best one.
>
>
>  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.
>>
>
> That was the known problem with using a..b


Yeah, but when I suggest a..b, I suggest applying existing [) range rules, as would be expected.

It doesn't make sense for enum keys, and I think the existing syntax is
acceptable for [] usage with enum keys, but you're not always switching on
enums.
Perhaps supporting both would be useful, where a..b is [) as expected, and
'case A: .. case B:' is [] for use with enums?

 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.
>>
>
> It originally was not required, but there was a campaign by a lot of D users to make it required to deal with the common bug of adding a value in one switch statement but forgetting to add it to another corresponding one.


Isn't that the entire point of final switch?
Why introduce final switch to address that, then do this aswell?

 if not for these strange decisions (purely for legacy compliance?).
>>
>
> I hope you'll find them less strange now.
>

Well, I get them, but it seems like an awful lot of missed opportunities. D
threatens to improve substantially on C switch, but it only goes half way,
resulting in something that's only slightly nicer if used precisely in the
'C way'.
It doesn't offer any new use cases, which is only really inhibited by some
quite trivial decisions.

Maybe a new and improved syntax for D that can be lowered?
'select()'? 'match()'?

I would set it up with proper scoping syntax:
match([x =] expression)
{
  case(1, 3, 4, 6) // use parentheses like all other control statements?
  {
    proper scope block...
    goto named_case;
  }

  case(10..20) // [) interval
    singleStatement();

  case(2, 5, 7..10) // this would be handy too
    singleStatement();

  case named_case // it's a useful concept; explicit named case for shared
work, which others can jump to. not sure how best to express it...
    sharedOperation();

  default
  {
    // surely this should be optional?
  }
}

I think a really useful construct could be made out of switch, but it seems that it won't happen because it must support C code unchanged.


February 17, 2014
On 17 February 2014 03:14, Timon Gehr <timon.gehr@gmx.ch> wrote:

> On 02/16/2014 04:42 PM, 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.
>>
>
> Yes it is supported. Use 'goto case;'.


That's still entirely explicit. It's not fallthrough.


 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.
>> ...
>>
>
> Like this: http://ceylon-lang.org/documentation/reference/ statement/switch/ ?
>

Yes. That's just common sense, right?


 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 ':'?
>> ...
>>
>
> AFAIK it is to emphasize that the range is inclusive, as opposed to:
>
> case 1..3:
>
> IIRC Walter's intention was to format it as follows:
>
> switch(x){
>
>     case 1:
>     ..
>     case 3:
> }
>

Right. Inclusive ranges are intended for enum ranges though. Integer ranges
are just fine in the usual way.
I'd support both...


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.
>> ...
>>
>
> Sure, mixing case lists and ranges in a single statement would be neat, but what would be your preferred syntax?
>

case 1, 2, 5..10, 20:

But that's not what I actually meant. I was actually talking about the case when 'case 1,2,5:' appears in the same switch() block as 'case 10: .. case 19:', when they appear next to eachother, it just looks awkward; like they don't belong together.


 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.
>> ...
>>
>
> There is more than one sensible default, so being explicit about the default makes sense.


Don't agree. Most of my cases are such that no default action is required.
It's just noise in my code.
If I fail to handle default when I probably should have, then that's
clearly my mistake.


The wasted lines are due to your formatting.
>
> int difficulty=-1;
>
> 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: break;
> }
>

I hate this. It violates the formatting conventions used EVERYWHERE else. In terms of formatting, it doesn't even look like the same language.


Of course, I'd just write the above as:
>
> int difficulty = e.note.note.between(60,108) ? (e.note.note-60)/12 : -1;
>

Yes yes, very clever. Obviously it's an example and could come in any shape
or form.
Personally, I also wouldn't do that anyway; basic readability has
definitely been lost.


 The default case is a total waste, since -1 should just be assigned when
>> initialising the variable above.
>> ...
>>
>
> How is the compiler supposed to know? It might be a logic error for e.note.note to be out of range.
>

Then I should have used an invariant or something actually designed for
catching values out of range.
It's not the compilers fault I made a logic error. I could make a logic
error literally anywhere in my code. Why force an annoying rule to maybe
sometimes catch exactly one case (while making other competing cases where
a logic error isn't present more annoying).


February 17, 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

February 17, 2014
On Sunday, 16 February 2014 at 15:43:31 UTC, Manu wrote:
>
> 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;
> }

I do agree that the breaks can get rather messy and I don't think that the current syntax is perfect. However I also find that in most situations where a switch statement is useful, it's not a bad idea to have it as a separate method anyways. In this case:

int getDifficulty(int note) {
    switch(note) {
    case 60: .. case 71:
        return 0;
    case 72: .. case 83:
        return 1;
    case 84: .. case 95:
        return 2;
    case 96: .. case 107:
        return 3;
    default:
        return -1;
    }
}

Since it's using a return there's no break required and it's arguably a cleaner separation.
February 17, 2014
On 2/16/2014 7:39 PM, Manu wrote:
> I think a really useful construct could be made out of switch, but it seems that
> it won't happen because it must support C code unchanged.

I tend to agree with Andrei on this - the proposals aren't fundamental or game changing, and are kinda just bouncing the rubble around (po-tay-to vs po-tah-to).

> Isn't that the entire point of final switch?
> Why introduce final switch to address that, then do this aswell?

Implicit in your questions is switches will only be used on enums, i.e. all the values a type can be are known to the compiler. This is only true for a smallish subset of types that are switched on.

final switch : for enums

default : for everything else
February 17, 2014
On 2/16/2014 10:39 PM, Manu wrote:
>
> Yeah, but when I suggest a..b, I suggest applying existing [) range rules,
> as would be expected.
>
> It doesn't make sense for enum keys, and I think the existing syntax is
> acceptable for [] usage with enum keys, but you're not always switching on
> enums.
> Perhaps supporting both would be useful, where a..b is [) as expected, and
> 'case A: .. case B:' is [] for use with enums?
>

There was a HUGE debate on all this back when the feature was first added, and every miniscule possibility and detail was thoroughly examined and argued ad nauseam.

IIRC, I was actually one the people not real happy with the syntax we have, but the thing is, it really isn't a big deal. It's just syntactical details on one particular construct. Yea, switch could be nicer looking, and it's not one of the nicer parts of D syntax, but the semantics are reasonably solid and that's the important part. The rest is just details and bikeshedding.

Personally, I would LOVE to see Nemerle's match statement/expression get into D, but other than that, out of all the tasks D faces, the switch syntax is pretty bottom-rung on both the importance and significance scales.