December 03, 2003
I think that you should not just code "any which way you want to" because your program will be full of bugs and will crash.  There are plenty of programming "styles" that are just plain bad bad bad and should be prohibited.

The compiler should try to help you get rid of bugs by detecting them at compile time, *before* you ship your application to millions of people.  I realize this is not 100% possible.

Sean

"Walter" <walter@digitalmars.com> wrote in message news:bqivh7$cr$1@digitaldaemon.com...
>
> "Ilya Minkov" <minkov@cs.tum.edu> wrote in message news:bqaci5$2rl3$2@digitaldaemon.com...
> > Requiring default will bring us to people typing "default: break;" instead of "default: assert(0)"  just exactly for the reason you stated!
>
> A similar problem has happened with Java where it required you to list the possible exceptions generated by each function. Even expert programmers
who
> publicly excoriated the practice would pepper their own code with generic catches just to shut up the compiler. The end result was that a rule that was supposed to increase robustness, actually wound up making things
worse.
>
> Philosophically, I don't think a language is robust because it requires programmers to code in a certain way. I think it's robust if it allows programmers to program the way they want to, making robust programming practices easier to use than non-robust ones. For example, although D
allows
> you to use pointers as you would in C, D provides better semantic alternatives (like out parameters) that are more robust, and easier to use than the pointer equivalents. It's a win-win.
>
> One can counter this by saying "why do static type checking at all, then? Why not do it at runtime?" It's an excellent point, and the answer is that it's just too expensive to do at runtime. Languages that rely on runtime type checking tend to run very slowly compared with statically typed languages. As always, everything is a compromise in programming, and so we have static type checking in D.


December 03, 2003
Carlos Santander B. wrote:
> [warning: some of you might not find this idea appealing.]
> 
> It was suggested before, and now I'd like to bring it up again. I don't
> think Walter will change the way switch works. 
I suspect you're right.

> Maybe he'll change the
> default: status, but fallthrough will remain, and that's because he wants
> some kind of C compatibility. (OT: btw, I don't agree with this. It's been
> said many times, and I repeat: people shouldn't just copy and paste C code
> for their D programs).
> 
> So maybe the next logical thing would be to add a different kind of switch
> with all the things we all (by majority) have asked for: fallthrough,
> explicit default, ranges, multiple case values, etc. What the keyword might
> be, I don't know. Maybe Basic's select would fit.

I think you might be on to something here.  I think "select" would be a good keyword for this appealing new way to control the flow.


This is how I'm forced to write it now:

switch(varName)
{
   case "icon":    iconFilename = varValue; break;
   case "tooltip": toolTip = varValue;      break;

   default: break; /* To prevent a runtime error if an invalid configuration setting is indicated. */
}

Windows doesn't usually generate a GPF if you put something it doesn't understand in the system registry.  I don't want my program to generate a runtime error if the user puts something untelligible in the configuration file.  My program will just skip that line.

This is how I'd like to write it:

select(varName)
{
   case "icon":    iconFilename = varValue;
   case "tooltip": toolTip = varValue;

   /* If I wanted to include a default, I would have done so. */
}

If the programmer wants an error to occur when a different value appears, they can specify it explicitly,
   case default: assert(0);

<rant>
Good programming practices can be encouraged by the language.  Good programming practices can't be compelled.  As the rules of a language approach compelling good practices, it would become unusable before it reached that apex.

If I'm wrong on this, please tell me which language compels good practices, and tell me how much you like developing in that language. From my perspective, the only safe language for me is the one that I don't use.
</rant>

I'm not trying to be obnoxious; this is just the way I look at it.

Justin

> 
> —————————————————————————
> Carlos Santander

December 03, 2003
Carlos Santander B. wrote:
> It was suggested before, and now I'd like to bring it up again. I don't
> think Walter will change the way switch works. Maybe he'll change the
> default: status, but fallthrough will remain, and that's because he wants
> some kind of C compatibility. (OT: btw, I don't agree with this. It's been
> said many times, and I repeat: people shouldn't just copy and paste C code
> for their D programs).
> 
> So maybe the next logical thing would be to add a different kind of switch
> with all the things we all (by majority) have asked for: fallthrough,
> explicit default, ranges, multiple case values, etc. What the keyword might
> be, I don't know. Maybe Basic's select would fit.

I suggested something like that a while ago. I'll just paste part of that post again here:

"""

If we wouldn't have the requirement to easily port C/C++ code to D (do we really have it?) I would like to suggest to ditch the classic switch statement completely and introduce a new construct with different syntax that is more consistent with the rest of the language. This would also solve the problem that different fallthrough behaviour might confuse C/C++ programmers, since it is immediately recognizable as a different thing.

Maybe something like this:

branch(expression)
{
    on(1)    someFunc();
    on(2,3,5..8)
    {
        a=7;
        dostuff(a,true);
    }
}

Hmmm. I think the syntax would probably need some work, but it would have some benefits:

- the on-blocks are real code blocks. Adding local variables to them works in the same way as it does anywhere else.
- break is not used in this construct, so it could actually be used to break out of an outer loop. This is something that I quite often wanted to do in C++. Ok, it is not THAT much of an issue in D anymore, since we can break with a label but being able to do it without an explicit label improves readability, since it is immediately clear which loop is referred to.

But since I don't see any chance that switch is ditched there is no real point to argue for such a statement anyway .

"""


Hauke


December 03, 2003
In article <bqkb30$21du$1@digitaldaemon.com>, Matthew Wilson wrote:
>> > It's very, very important that programs fail in a controlled manner when they encounter unanticipated conditions.
>>
>> [a lot of debate]
>>
>> Even better, demand the default case at compile time. Then it will never be a problem at runtime!
> 
> Exactly. What could be simpler?

There's been a lot of debate on the matter and a lot of different strong opinions, but I don't think we've still gone to the root of the problem. Namely:

Which one is the primary usage of switch statement?  One where you cover all the cases, or the one where you cover them only partially?

Let's consider the alternatives.

1. If the programmer's intent is to cover all of the cases, then by
   default it should throw an exception if an unexpected value is
   encountered. Examples of this sort follow below.

2. If the intent is to cover only some of the cases, then the default
   behavior should do nothing. As an example of this:

void handle_keypress(int code)
{
    switch (code)
    {
    case 'q': quit(); break;
    case 'w': do_something_else; break;
    case ...:
    ...;
    // would not be needed if these kind of switches is common
    default:
        break;
    }
}

Here a default statement is required, and if it is not there, the programmer will notice very quickly when an exception flies. However, if it wouldn't be required (as in C/C++), not much harm would be done.

Let's go back to the case 1: when we actually want to handle all cases.

A classic representative of "handle all cases" comes when doing a switch over an enum:

enum Weekday { mon, tue, wed, thu, fri, sat, sun }

void handle(Weekday w)
{
    switch (w)
    {
    case mon: handle_mon(); break;
    case tue: handle_tue(); break;
        // and so on...
    }
}

Now if you want to add a new weekday (ok, I might've picked my example a bit short-sightedly...), and forgot to add it to the switch statement, you should probably get an exception (or mayhap a warning, or even an error?) for not adding it to the switch statement. But this isn't often the way anyone should be programming at all. (Someone may disagree, though...) It's reasonably common to make Weekday a polymorphic object in this case:

interface Weekday { void handle(); }
class Monday : Weekday { void handle() { ... } }

void handle(Weekday w)
{
    w.handle();
}

Then the 'missing case' of the switch statement won't be even left for the language runtime to notice, because it will be detected during the compilation! And everyone is happy.

However, what if the value for the switch statement where we want to handle all possible cases doesn't come from the program itself but from outside, suppose while, say, lexing or parsing a source file in a compiler front-end, or maybe reading a file in some graphics format:

void read_file()
{
    File_Header hdr = get_header();

    switch (hdr.bit_depth)
    {
    case 24: set_depth(24); break;
    case 16: ... break;
    case 15: ... break;
    case 8: ... break;
    // No default would have to be generated because we always want to
    // specify one:
    default:
        throw Invalid_File_Format_Exception(
            "Unknown bit depth " + hdr.bit_depth.to_string());
    }
}

Ha! Here the automatically generated exception wouldn't help us anyway, because in this case we want to handle the default statement ourselves!

So the only place where the automatically thrown exception does give any benefit is when there is an enum for which there is an excuse of not making it an object. Right? Then is there much sense in having the throw-exception behavior the default? Maybe someone knows good excuses?

And I can't really say which of these cases (handle-all/handle-some) is the more common one. Maybe two different keywords were better from the documentative viewpoint.

What do you people generally use "switch" for?


-Antti

P.S. By the way, to my ear the pascalese "case x of" sounds much like the case "now we're handling *all* of the possible alternatives". And the C-style "switch" really only deals with only some of the possible cases and is similar in semantics to if-elseif-elseif... withouth the final else.

December 04, 2003
> Maybe something like this:
>
> branch(expression)
> {
>      on(1)    someFunc();
>      on(2,3,5..8)
>      {
>          a=7;
>          dostuff(a,true);
>      }
> }
>
> Hmmm. I think the syntax would probably need some work, but it would have some benefits:
>
> - the on-blocks are real code blocks. Adding local variables to them
> works in the same way as it does anywhere else.
> - break is not used in this construct, so it could actually be used to
> break out of an outer loop. This is something that I quite often wanted
> to do in C++. Ok, it is not THAT much of an issue in D anymore, since we
> can break with a label but being able to do it without an explicit label
> improves readability, since it is immediately clear which loop is
> referred to.
>

I like it. I think branch is a good name as well.



December 04, 2003
Me too, I like it. Maybe D should implement both versions: switch and branch. As do-while and while...


In article <bqmebi$21sh$1@digitaldaemon.com>, Vathix says...
>
>> Maybe something like this:
>>
>> branch(expression)
>> {
>>      on(1)    someFunc();
>>      on(2,3,5..8)
>>      {
>>          a=7;
>>          dostuff(a,true);
>>      }
>> }
>>
>> Hmmm. I think the syntax would probably need some work, but it would have some benefits:
>>
>> - the on-blocks are real code blocks. Adding local variables to them
>> works in the same way as it does anywhere else.
>> - break is not used in this construct, so it could actually be used to
>> break out of an outer loop. This is something that I quite often wanted
>> to do in C++. Ok, it is not THAT much of an issue in D anymore, since we
>> can break with a label but being able to do it without an explicit label
>> improves readability, since it is immediately clear which loop is
>> referred to.
>>
>
>I like it. I think branch is a good name as well.
>
>
>


December 05, 2003
Someone has probably already mentioned this, but C# <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/csref /html/vclrfTheSwitchStatement.asp">solved</a> the problem by keeping the switch syntax, but disallowing fall-through. Instead you can do one of two things: you can stack case statements, like so:

switch (index) {
  case 0:
  case 1:
    do_something();
    break;
}

And you can goto another case, like so:

switch (index) {
  case 0:
    do_something();
    goto case 1;
  case 1:
    do_something_more();
    break;
}

It doesn't address the question of throwing exceptions on unhandled cases, but it neatly solves most of the issues brought up so far.  Can anyone give an example of some code that relies on the fall-through behavior that this won't solve?

Dan L.


December 05, 2003
Dan Liebgold wrote:
> Someone has probably already mentioned this, but C# <a
> href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/csref
> /html/vclrfTheSwitchStatement.asp">solved</a> the problem by keeping the
> switch syntax, but disallowing fall-through. Instead you can do one of two
> things: you can stack case statements, like so:
> 
> switch (index) {
>   case 0:
>   case 1:
>     do_something();
>     break;
> }
> 
> And you can goto another case, like so:
> 
> switch (index) {
>   case 0:
>     do_something();
>     goto case 1;
>   case 1:
>     do_something_more();
>     break;
> }
> 
> It doesn't address the question of throwing exceptions on unhandled cases,
> but it neatly solves most of the issues brought up so far.  Can anyone give
> an example of some code that relies on the fall-through behavior that this
> won't solve?

Well, there's nothing that a goto can't "solve."  :-)  In fact, it's part of my own suggestion.

...which is:

1. case labels should allow enumerations:

	case 1, 3, 5:

2. case labels should allow ranges:

	case 6 .. 25:

3. the compiler should generate an implicit "break" at the end of each case - no fall-thrus, ever.

4. the programmer can use normal program labels and "goto" for every other need.

December 05, 2003
The following is cut from D lexer.c:

case '/':
p++;
switch (*p)
{
case '=':
p++;
t->value = TOKdivass;
return;

case '*':
p++;
while (1)
{
while (1)
{
switch (*p)
{
case '/':
break;

case '\n':
loc.linnum++;
p++;
continue;

case 0:
case 0x1A:
error("unterm. /* */");
p = end;
t->value = TOKeof;
return;

default:
p++;
continue;
}
break;
}
p++;
if (p[-2] == '*' && p - 3 != t->ptr)
break;
}
continue;

case '/':
p++;
..

It looks as if you really can't code a lexer if there were
implicit breaks. But I think this would work the same
with them?

Of course an empty case should not get an implicit break.

I'm too old to be sure, but is there any place in phobos where an implicit break would break the code? (No pun intended.)


December 05, 2003
The D if-statement demands a {} instead of a semicolon.
The switch statement could do the same with the default
cause. Leave it out, and you get a compiler error.

If you want to not do anything, then just write
default
{}
and everyone should be happy.

"It should not be impossible to excercise bad habits,
it just shouldn't be too easy."

PS, why doesn't the if statement issue a runtime
exception at a missing {} ? Shouldn't the compiler
behave the same in similar situations?   :-)