Thread overview | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
February 16, 2014 switch() | ||||
---|---|---|---|---|
| ||||
Attachments:
| 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 Re: switch() | ||||
---|---|---|---|---|
| ||||
Posted in reply to Manu | I think the main reason why switches work this way (and cannot/should not be changed) is because of one of the main design goals of D: http://dlang.org/overview.html #7: Where D code looks the same as C code, have it either behave the same or issue an error. The proposed changes would violate this, making existing C code change meaning silently (wrt implicit "break;" in particular). TBH, though, I agree that this is somewhat of a speed bump in D. I really rarely find myself using switches because of the same issues you mention. Unfortunately, without adding new syntax (which is also problematic for many reasons), such a thing cannot be fixed unless this goal is ignored. |
February 16, 2014 Re: switch() | ||||
---|---|---|---|---|
| ||||
Posted in reply to Chris Cain | On Sunday, 16 February 2014 at 16:04:53 UTC, Chris Cain wrote:
> http://dlang.org/overview.html
>
> #7: Where D code looks the same as C code, have it either behave the same or issue an error.
I feel the need to clarify: I really don't mean all of the proposed changes are bad for this reason, but one of the ones I would find most value in (implicit "break;"s) is inappropriate. Sorry for the double post.
I do like the suggestions in spirit.
** I'm also surprised "default" is necessary. I thought it was only necessary in final switches.
|
February 16, 2014 Re: switch() | ||||
---|---|---|---|---|
| ||||
Posted in reply to Chris Cain Attachments:
| On 17 February 2014 02:04, Chris Cain <clcain@uncg.edu> wrote: > I think the main reason why switches work this way (and cannot/should not be changed) is because of one of the main design goals of D: > > http://dlang.org/overview.html > > #7: Where D code looks the same as C code, have it either behave the same or issue an error. > > The proposed changes would violate this, making existing C code change meaning silently (wrt implicit "break;" in particular). > Okay, but that doesn't address the range cases... TBH, though, I agree that this is somewhat of a speed bump in D. I really > rarely find myself using switches because of the same issues you mention. Unfortunately, without adding new syntax (which is also problematic for many reasons), such a thing cannot be fixed unless this goal is ignored. > I agree, I rarely use switch statements for the same reason. Deliberately gimping a very useful control statement because it's shit in another language (it really is!), seems like an extremely lame design goal to me. switch could be so much more useful with these changes. I suspect most people would avoid it much of the time for these reasons. |
February 16, 2014 Re: switch() | ||||
---|---|---|---|---|
| ||||
Posted in reply to Chris Cain Attachments:
| On 17 February 2014 02:08, Chris Cain <clcain@uncg.edu> wrote:
> ** I'm also surprised "default" is necessary. I thought it was only necessary in final switches.
>
final switch requires you to implement every possible case, so it makes no sense in that case.
|
February 16, 2014 Re: switch() | ||||
---|---|---|---|---|
| ||||
Posted in reply to Manu | 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:
if( _keyHandler ) {
_keyHandler( cast( Key )event.key.keysym.scancode,
cast( KeyState )event.key.state,
cast( KeyModifier )event.key.keysym.mod );
}
break;
case SDL_WINDOWEVENT:
handleWindowEvent( &event.window );
break;
case SDL_QUIT:
if( _quitHandler ) {
_quitHandler();
}
break;
default: break;
}
|
February 16, 2014 Re: switch() | ||||
---|---|---|---|---|
| ||||
Posted in reply to Manu | 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;'. > With this in mind, 'break' is unnecessary. Sure. > Why is it required? Backwards compatibility. > 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/ ? > 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: } > 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? > 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. > 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 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; } Of course, I'd just write the above as: int difficulty = e.note.note.between(60,108) ? (e.note.note-60)/12 : -1; > 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. >... > > 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; > } > ... Ideally closer to: int difficulty = switch(e.note.note){ 60 .. 72 => 0; 73 .. 84 => 1; 84 .. 96 => 2; 96 .. 108 => 3; _ => -1; }; |
February 16, 2014 Re: switch() | ||||
---|---|---|---|---|
| ||||
Posted in reply to Manu | 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. > 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 > 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. > if not for these strange decisions (purely for legacy compliance?). I hope you'll find them less strange now. |
February 16, 2014 Re: switch() | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | On 2014-02-16 21:03, Walter Bright wrote: > 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 what final switches are for? Or are default statements not allowed in final switches at all? -- /Jacob Carlborg |
February 16, 2014 Re: switch() | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jacob Carlborg | On 2/16/2014 12:06 PM, Jacob Carlborg wrote:
> On 2014-02-16 21:03, Walter Bright wrote:
>
>> 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 what final switches are for?
Final switches only help for enums.
> Or are default statements not allowed in final switches at all?
Right, they aren't.
|
Copyright © 1999-2021 by the D Language Foundation