September 16, 2003
How about a different word for case, like bcase, casex, fcase or ... briefcase. I don't know.

switch(foobar)
{
case 3: normalFallThrough();
bcase 4: breaksAfterThis();
bcase 5, 6, 7: goodIdea();
default: yes();
}




September 16, 2003
Oh, so we're going to start specifying a new syntax for the switch statement? Cool!

Because I recently decided that I would always put braces around the statements in cases (to more obviously group them visually) (I also always use braces with ifs, fors, and whiles -- more coherent visual presentation) I propose that they be required, so howsabout:

switch ( expr )
{
case ( 1 )
{
..
break;
}

case ( 2 , 3 )
{
..
nobreak;
}

default
{
..
break;
}
}

An additional benefit of this syntax is that it removes the colon, thereby freeing it up for use in the assignment operator(s).

John Boucher -- Quite contrary
The King had Humpty pushed.
September 16, 2003
> > I like the idea too (and I know C# does it), but I wonder if there are
> fewer
> > errors caused by this than by  if ( x = y )  which Walter has done away
> with,
> > although I don't entirely agree with the way he did.
>
> Good point, I love the assignment's in conditionals too.  That was one of the problems I had with python.  Walter what can we do ( if anything ) to convince you to allow this and the nobreak ?
>
> Charles

Uses := for assignment instead of =...

:= would always be required in conditional... and IMO should always be required but we could allows = where it is safe to assumes that the user want to do an assignment.

int a = b;    // OK to uses = here

a = b;    // could be OK here since the result is not used.

(a = b).callAMember();    // would be ok if member is not also a
                                        // member of bool.

if (a := b) { }    // Here := is required (or extra ())


September 16, 2003
> I recall that there are good reasons for not having compile-time options that change the semantics of the code. If my memory is not failing me, Walter said somewhere that he has a rather strong opinion about this.
>
> Generally I think the same, but I'd allow the following border case:
>
> - Default behavior of switch statement is to break.
> - Keyword "fallthrough" is required to, well, fall through.
> - A compiler-switch "-require-explicit-break-or-fallthrough" that will
>   require explicit "break;" or "fallthrough;". The purpose of this
>   switch is to make porting C apps easier.
>
> That way you could write native D apps without writing the tedious break
> statement. After all, as Philippe said, the behavior in 9 out of
> 10 cases is to break. The superfluous breaks just clutter up the code.
>
> If you'd need to port C apps, just use the option -require-explicit-break-or-fallthrough and insert "fallthrough" where there is no break. After the code compiles, you can remove the option and works correctly. There will be a couple of redundant break statements but they won't hurt anyone.
>
> -Antti
>

I agree with that solution where the compiler could find potential errors when porting code or for people that have learn that other language want to avoid common pitfall (here would think it fall through).

OTOH I think that requiring both break and nobreak (or
fallthrough) would be better since it remove the need of
an extra compiler option that some will want to uses
anyway (like those that compile at level 4 under Visual C++
even if it give far too much warning for correct code...)


September 16, 2003
>
> And BTW, Walter, while we're on the subject... how about multiple-value
case
> labels, a la VB?
>
> switch (x)
> {
> case 0:
>     ...
> case 1, 3, 5:
>     ...
> case 2, 4, 6 to 10:  // or 6..10 as in Pascal, or [6, 10] or whatever
>     ...
> case is < 1:  // see comments on this one below
>     ...
> case 11, 13 to 15, is > 16:  // All together now!
>     ...
> default:  // no, I'm not proposing a "case else" :)
> }
>

For me, multiple values is OK and range also (with either syntax)

But for the is operator, I'm not sure... I think those case
could be handled if the default case with regular if...
OTOH, this might be a good solution particulary
if we add the nodefault (see below). IMO, is
is surperfluous. We could have < 1 or .. 1 or
[ , 10] but maybe it should be required just to
ensure that there are no unintended errors...

I think that each range should be mutually exclusive
so that it is illegal to have the same value twice
once explicit and once in a range.

We should have also an option to specify that each possible values must have a label for case when all cases must be handled. This would typically be usefull for enumerations where we might want to ensure that all values appears.

switch (enumVar)
{
case e1 :
    break;

case e2 :
    doSomething();
    nobreak;

case e3 :
    doSomethingElse();
    break;

nodefault:    // All valid value for the type must appears in the switch }

This would be great for cases where an enum is needed and we must handle all case...


September 16, 2003
In article <bk6vo2$1sdp$1@digitaldaemon.com>, Hauke Duden wrote:
> Antti Sykäri wrote:
>> Of course, you *could* make the switch statement a generalization of the old one without losing optimizations.
> 
><snip>
> 
>> The compiler will in this case have to recognize the "old-style" switch statements (where all cases are constant and maybe not overlapping), which should be trivial, and perform the usual optimizations on them. More complex statements (where some case is non-constant) are implemented as an if-elseif-etc-else statement.
> 
> I don't know. To me this seems to be a feature that complicates the the language (and the compiler) with not much of a real benefit. If you need if-capabilities, why not use if? You might need a temporary variable to store the "switch value", but that is really the only downside I see.

The compiler might be slightly more complicated, or then again it could actually be even simpler or as simple. A quick'n'dirty compiler might implement "switch" simply by translating it to an if-else sequence, and an optimizing compiler could do as the optimizing C compilers do, if all values in case statements are known at compile time.

On the other hand:

- there would be less problems with:
  - "Why can't I use struct
    values/classes/enums/your-arbitrary-expression-here in the switch
    statement?"
  - And what the heck is so special about those that they can't
    be evaluated at runtime like everything else?

- added ease for the maintenance programmer...  For example, consider that you have an interactive environment which is used to "frobnicate" and "gref" and the older version also supported some features that now are deprecated. You might write a simple interpreter for it like this:

    char[] kw = read_keyword();
    switch (kw)
    {
    case "frobnicate":
        do_frob(); break;
    case "gref":
        do_gref(); break;
    case "quit":
        quit(); break;
    case "xixaxa_xoxaxa_xuxaxa": // FALL THROUGH
    case "kirje":
        printf("Sorry, command %d is no longer supported.\n", kw);
        print_help();
        break;
    default:
        printf("Unknown command %d\n", kw); break;
        printf_help();
    }

Interpreter works fine. Now your manager comes into the picture and says: "I need that localized in 10 languages, ASAP. Oh yeah, the keywords need to be localized too."

So you decide to localize the whole codebase by first automatically extracting all the strings that exist in the program (say, 100000 lines) with a perl script and then search-and-replacing each string, for example, "xyzzy" with localize("xyzzy"). localize() is a global function that replaces xyzzy with whatever is appropriate in the current locale.

"Yeah that's simple", you say to your manager, "I'll take at most a couple of hours to do the search & replace and write the localization code"

Then you do it (15 minutes it takes), and result is:

    switch (kw)
    {
    case localize("frobnicate"): dothis(); break;
    case localize("gref"):
    ...etc...
    }

You start doing manual, repetitive, error-prone work: converting switch statements to if-else statements.  At the end of the week your boss asks "What took you so long? By the way, we need the localization in an other branch of the software delivered to customer X two months ago, but I guess it's simple to do since you already did it" and there we go again...

This is just an extreme example of how orthogonal language features can be useful. Sometimes real life can be extreme, too.

To summarize, what I hoped earlier was that the mechanics of the "switch" statement would be driven by its semantics rather than its implementation (switch is often compiled as a table lookup, using linear search, hashing or even binary search, and for that reason they have been restricted to constant values of basic types -- because it's easiest to produce efficient code for them. Dunno what D compiler does with string literal lookups, though -- is it a trade secret? <grin>).

I admit that being driven by implementation can make you feel sort of close to machine, which is sort of a good thing. Efficient-looking constructs often end up being efficient, too. This is part of the fascination in C. But sometimes you want things that Just Work, and we seem to be steadily traveling away from the bare metal anyway.

I'm not suggesting making "switch" work on non-constant values would be a critical feature in the language, just some extra cream on the cake.

This issue also touches things like reference to bit[], template arguments of different types, what-have-you. There should be as few special cases as possible.

> Upsides of leaving the switch-case statement simple are:
> 
> - less complicated compiler
> - no need to invent new syntax to express <,>,<=, is-in-interval [a..b],
> ]a..b], [a..b[, ]a..b[ etc.

Sorry if you got me wrong, I didn't mean to propose those. I believe the
language has too much syntax already. Adding extra operators feels the
same as adding new icons to your favorite Windows program's toolbar.
Some Windows programs that I use have tons of tiny icons and I don't
have the faintest of what they actually do. Just having a button with
a caption would suit me fine, thank you. Or, in the programming language
area, a method or function which does the same.

For example, "x is_in_interval [1..5]" might mean 1 <= x && x <= 5 (or <
5 if you want it the Dijkstra way) in a hypothetical programming
language. Why not have [1..5] denote a constant of type Range, and have
a method Range.has(int)? Then you'd have "[1..5].has(x)". But why end
there if you can have "Range(1, 5).has(x)". (Well, maybe operators are
sometimes nice after all.)

> - You can instantly be certain that you are looking at "flat" code (i.e. code where no operator overloads and stuff like that are involved) when you see a switch statement.

Hmm, strangely I have no special desire to be certain that there wouldn't be operator overloading or other features that belong into a modern language. I should be able to trust that the operators I use are overloaded properly (so that they convey the right semantics).

Oops, what a rant this grew up to be... better stop and do something useful for a while ;)

-Antti

September 16, 2003
I am sorry that my statement came out a bit garbeled.
My intension was to say that I appreciated your syntax because it suggested the
possibility of a cleaner implementation of the 'switch' statement.
It even leaves both keywords 'break' and 'nobreak' redundant.
The reason is that the semantics of both keywords could be interpreted to be
implicit in the syntax:

switch(n)
{
case 4 [..mplicit nobreak..], 5:
{
// Some code
}
[..implicit break..]

default:
{
// Some code
}
}

The benefit I saw was was that :
1) A new keyword 'nobreak' is not needed. Fallback (nobreak) through null-code
is allowed, when cases are stated on the same line.
2) The old keyword 'break' is not needed because it could be decided always to
be be implicit. Thus, it would not any longer be possible to create a bug by
forgetting 'break' because there is no 'break' to forget.
3) Clean value/code-block mapping is enforced. i.e. implicit breaks would
prevent programmers from fragmenting code blocks into conditional parts and
spread them over several 'case'es (a bad practice that is possible in C/C++).

erik
In article <bk714l$20ms$2@digitaldaemon.com>, Riccardo De Agostini says...
>
>"Erik Baklund" <Erik_member@pathlink.com> ha scritto nel messaggio news:bk6vgp$1rn2$1@digitaldaemon.com...
>> Please not that the suggested syntax leaves both 'break' and 'nobreak' redundant.
>
>Of course: break should be implied. Thanks for pointing that out.
>
>Ric
>
>


September 16, 2003
In article <bk7ev1$alu$1@digitaldaemon.com>, Philippe Mori says...
>
>> > I like the idea too (and I know C# does it), but I wonder if there are
>> fewer
>> > errors caused by this than by  if ( x = y )  which Walter has done away
>> with,
>> > although I don't entirely agree with the way he did.
>>
>> Good point, I love the assignment's in conditionals too.  That was one of the problems I had with python.  Walter what can we do ( if anything ) to convince you to allow this and the nobreak ?
>>
>> Charles
>
>Uses := for assignment instead of =...
>
>:= would always be required in conditional... and IMO should always be required but we could allows = where it is safe to assumes that the user want to do an assignment.
>
>int a = b;    // OK to uses = here
>
>a = b;    // could be OK here since the result is not used.
>
>(a = b).callAMember();    // would be ok if member is not also a
>                                        // member of bool.
>
>if (a := b) { }    // Here := is required (or extra ())
>
>

Uh, I hope I didn't suggest having two different assignment operators to do the same thing, with one being used in tests and the other one not. That's just wrong.

I _did_ suggest replacing the bare equal sign (=) assignment operator with a colon-equal (:=) a la Pascal, and I stand by that. To anger several of you further allow me to go several steps farther over the edge and suggest redoing all the assignment operators to include a colon...

=   becomes :=
+=  becomes :+
-=  becomes :-
*=  becomes :*
/=  becomes :/
%=  becomes :%
<<= becomes :<<
>>= becomes :>>
!=  becomes :!
&=  becomes :&
|=  becomes :|
^=  becomes :^
~=  becomes :~

Did I forget any?

If not for D, then for the language that is sure to come after D.


Oh, oh, oh -- I just had an even whackier idea...
Howsabout we require something to tell the compiler that we really do want to do
the assignment in the test? Like:
[safe]
if ( x = y ) ... ;

Kinda sorta like C#.

John Boucher
The King had Humpty pushed.
September 16, 2003
>
> Then you do it (15 minutes it takes), and result is:
>
>     switch (kw)
>     {
>     case localize("frobnicate"): dothis(); break;
>     case localize("gref"):
>     ...etc...
>     }
>

Here I suppose you would like that every items be compared in turn for equality with kw until a match is found...

And what we should do if I want to uses something else than
operator== for the comparison ? It would then be nice to be able
to pass a function/delegate/function object/... that would be used
for the comparison similat to what we have in C++ STL where
many algorithm accept user predicate...

> This is just an extreme example of how orthogonal language features can be useful. Sometimes real life can be extreme, too.
>
> To summarize, what I hoped earlier was that the mechanics of the "switch" statement would be driven by its semantics rather than its implementation (switch is often compiled as a table lookup, using linear search, hashing or even binary search, and for that reason they have been restricted to constant values of basic types -- because it's easiest to produce efficient code for them. Dunno what D compiler does with string literal lookups, though -- is it a trade secret? <grin>).
>

Well if this is allowed then it mean that the compiler would be able to
find duplicate case which is a usefull feature of case... and I'm not sure
that having different rules on how it works depending on weither
the expression is a constant or not is a good idea...

This simple change allows for bugs that would be caused by misusing a variable a constant (for example using a enum variable instead of a value defined inside of the enum).

If switch is extended in such a way, a would prefer a solution where would have to explictly specify a function for comparing the values:

bool myfunction(char[] expr, char[] caseValue)
{
    return localize(caseValue) == expr;
}

switch (x, myfunction)    // I'm not sure of the syntax for passing a
function
    // or a delegate or sothing similar
{
case "unlocalized1" :
    break;

case "unlocalized2" :
    break;
}

That way, we would be able to localize without replacing the switch but global find and replace won't works either...

But that syntax has some limitations: First it won't works for range or condition like < 5. Second, we can check only one thing at a time... So while it is better, the solution is not as general as someone could like it....

And I think it would be preferable to have a syntax that allows ranges
like < 5 or 1..5. Maybe we should always uses a..b syntax and
uses special constant min/max or -INF/+INF for range that are not
bounded on one side:

case min..5 :
case -INF..5 :

Also it would be interesting to support range that include/exclude
limit for types that are not finite (double, strings,...). Maybe, [, (, )
and
] should be used as required... or we might have a postfix + or -
to indicate if the value is include or not:

case 5+..9.5+ : // from 5 (not included to to 9.5 included)
case 5..9 : // 5 to 9 both ends included
case 5-..9+ : // Idem
case 5+..9-- : // 5 to 9, ends not included

>
> For example, "x is_in_interval [1..5]" might mean 1 <= x && x <= 5 (or <
> 5 if you want it the Dijkstra way) in a hypothetical programming
> language. Why not have [1..5] denote a constant of type Range, and have
> a method Range.has(int)? Then you'd have "[1..5].has(x)". But why end
> there if you can have "Range(1, 5).has(x)". (Well, maybe operators are
> sometimes nice after all.)
>

If we allows something like Range(1, 5).has(x), then it means that when
we look at the switch expression, we will never be sure that only that
is used to evaluate which case to select... since one could uses a function
with side effect or reference a variable that is not is the switch
expression.

IMO we should only extent switch to support range and support any type of constant (if this is not already the case).

The main difference between if/else and a switch is that a switch is
supposed to select a case from an expression... And I thing that
we should be able to have arbitrary expression for each case as this
would allows side-effect and subtle bugs... caused by the fact that
the cases do not depend only on the expression...

Also, I think that a switch should be independant on the order
of each case and reordering them should not changes the output
but might only affect the performance if the optimizer assumes that
first cases are more probable...


September 16, 2003
> I am sorry that my statement came out a bit garbeled.
> My intension was to say that I appreciated your syntax because it
suggested the
> possibility of a cleaner implementation of the 'switch' statement.
> It even leaves both keywords 'break' and 'nobreak' redundant.
> The reason is that the semantics of both keywords could be interpreted to
be
> implicit in the syntax:
>
> switch(n)
> {
> case 4 [..mplicit nobreak..], 5:
> {
> // Some code
> }
> [..implicit break..]
>
> default:
> {
> // Some code
> }
> }
>
> The benefit I saw was was that :
> 1) A new keyword 'nobreak' is not needed. Fallback (nobreak) through
null-code
> is allowed, when cases are stated on the same line.
> 2) The old keyword 'break' is not needed because it could be decided
always to
> be be implicit. Thus, it would not any longer be possible to create a bug
by
> forgetting 'break' because there is no 'break' to forget.
> 3) Clean value/code-block mapping is enforced. i.e. implicit breaks would
> prevent programmers from fragmenting code blocks into conditional parts
and
> spread them over several 'case'es (a bad practice that is possible in
C/C++).
>
> erik

And how do you handle fall-through? With a goto (maybe something like goto
case(4) where one would give the expression that indicate the target; if
this
expression is constant, the compiler can make a direct jump; otherwise we
switch on that new value). This would even be more powerfull that any other
solution I have seen presented:

int some_value = ...;
switch (some_expr())
{
case 1 :
    goto case(some_value);    // redo the switch with that new value.

case 2:
    goto case(1);        // compile-time jump to case 1

case 3 :
    do_something();
    goto case(default);    // default is a special value for jumping to
default

default:
    do_something_else();
}