[comp.lang.c] The D Programming Language: switches

ray@micomvax.UUCP (Ray Dunn) (03/16/88)

[References to various postings giving various proposed incarnations of
switch syntax ommitted]

My 2-penny-worth is that the switch statement is anyway too restrictive with
its integer constant expression values, and that making a special case [pun
not intended] of this was Not Really Worthwhile.

Case statements should just be generalised into a "shorthand" way of
stating if...elseif...elseif...else...

So, (simplified to give jist rather than be definitive):

switch
{ [declaration]
  case (expression):
    .
    [statement]
    .

  case (expression):
    .
    [statement]
    .

  default:
    .
    [statement]
    .
}

Where the expression in each "case (expression)" is a full boolean
expression as in "if (expression)".

There could of course be a (bad) argument to say that if "switch {" appears
as "switch (expression) {" then the previous constant expression meaning of
the switch is used - pretend I didn't suggest that.

Automatic fallthrough should *not* take place, and the common fallthrough
requirement of:

case 1:
case 2:
case 3:

becomes a simple 'or'ed boolean expression.

Compilers can easily generate just as "efficient" code when only constant
expression values are involved (MSC 4.0 seems to generate worse code when you
use a case statement than when you use an if...else if...else... sequence!).

[God! I've just realized that this is aprroximately the way case statements
are defined in that *wonderful* (:-) language dBaseIII - so, I know, I've
just lost any credibility I may have had!!]


Ray Dunn.  ..{philabs, mnetor, musocs}!micomvax!ray

franka@mmintl.UUCP (Frank Adams) (03/17/88)

In article <941@micomvax.UUCP> ray@micomvax.UUCP (Ray Dunn) writes:
>Case statements should just be generalised into a "shorthand" way of
>stating if...elseif...elseif...else...

Permit me to register my strong disagreement.  if...elseif...elseif...else...
should be written:

if (...) {
	...
} else if (...) {
	...
} else if (...) {
	...
} else {
	...
}

(positioning the brackets as you see fit.  Please let's not start *that*
discussion again!)

Computer languages don't need two equally attractive ways to say the same
thing.
-- 

Frank Adams                           ihnp4!philabs!pwa-b!mmintl!franka
Ashton-Tate          52 Oakland Ave North         E. Hartford, CT 06108

karl@haddock.ISC.COM (Karl Heuer) (03/21/88)

In article <941@micomvax.UUCP> ray@micomvax.UUCP (Ray Dunn) writes:
>Case statements should just be generalised into a "shorthand" way of
>stating if...elseif...elseif...else...

I disagree.  The else-if chains already provide a perfectly good way of doing
that, and are just as concise.  There's no need to add a second syntax of
equivalent power.  In fact, the existing switch statment is more concise in
the job for which it was designed: you can write "case VALUE:" instead of
"case (long_hairy_expression == VALUE):".  If the controlling expression has
side effects, your "enhanced" switch would require an extra temporary.

Yes, in a sense switch is less powerful than an else-if chain.  Let's keep it,
for the same reasons that we retain flow constructs less powerful than goto.

The existing switch isn't perfect, of course.  I do think we should get rid of
automatic fallthrough, and allow a list of values (or a list of ranges) for
each label.  Then the case-blocks would be commutative.

>Compilers can easily generate just as "efficient" code when only constant
>expression values are involved

Oh?  Do any compilers currently optimize if-else statements in this way?

>(MSC 4.0 seems to generate worse code when you use a case statement than when
>you use an if...else if...else... sequence!).

A compiler that doesn't optimize the existing switch is unlikely to do
anything intelligent with the generalization.

Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint

g-rh@cca.CCA.COM (Richard Harter) (03/22/88)

In article <3074@haddock.ISC.COM> karl@haddock.ima.isc.com (Karl Heuer) writes:
>In article <941@micomvax.UUCP> ray@micomvax.UUCP (Ray Dunn) writes:
>
>Yes, in a sense switch is less powerful than an else-if chain.  Let's keep it,
>for the same reasons that we retain flow constructs less powerful than goto.
>
	Theoretically the switch construct is more powerful than an else-if
chain because it selects in one step.  Execution is O(1) rather than O(n)
where n is the number of branches.  It is, for example, considerably more
efficient to use a switch with a thousand cases than an else-if chain with
a thousand elses.  [This is not to be taken as an endorsement of such
code. :-)]
-- 

In the fields of Hell where the grass grows high
Are the graves of dreams allowed to die.
	Richard Harter, SMDS  Inc.

edw@IUS1.CS.CMU.EDU (Eddie Wyatt) (03/22/88)

 >>
 >>Yes, in a sense switch is less powerful than an else-if chain.  Let's keep it,
 >>for the same reasons that we retain flow constructs less powerful than goto.
 >>
 > 	Theoretically the switch construct is more powerful than an else-if
 > chain because it selects in one step.  Execution is O(1) rather than O(n)
 > where n is the number of branches.  It is, for example, considerably more
 > efficient to use a switch with a thousand cases than an else-if chain with
 > a thousand elses.  [This is not to be taken as an endorsement of such
 > code. :-)]

  I think he meant in terms of expressability.  Which is true in a
practical sense, false in theorectically.  'switch' and 'else-if'
are equivalent in terms of expressability.


-- 

Eddie Wyatt 				e-mail: edw@ius1.cs.cmu.edu

barmar@think.COM (Barry Margolin) (03/23/88)

In article <1186@PT.CS.CMU.EDU> edw@IUS1.CS.CMU.EDU (Eddie Wyatt) writes:
> >>Yes, in a sense switch is less powerful than an else-if chain.  Let's keep it,
> >>for the same reasons that we retain flow constructs less powerful than goto.
> > 	Theoretically the switch construct is more powerful than an else-if
> > chain because it selects in one step.  Execution is O(1) rather than O(n)
> > where n is the number of branches.  It is, for example, considerably more
> > efficient to use a switch with a thousand cases than an else-if chain with
> > a thousand elses.  [This is not to be taken as an endorsement of such
> > code. :-)]
>  I think he meant in terms of expressability.  Which is true in a
>practical sense, false in theorectically.  'switch' and 'else-if'
>are equivalent in terms of expressability.

That is obviously untrue.  A switch can only check one expression, and
can only compare it against constant values.  An else-if chain can
check for multiple conditions and can compare computed values.  You
can even have side effects in the conditional expressions, which can
affect later conditionals (I don't recommend this practice taken to
extreme, but things like 'if ((ch = getchar()) == EOF) ... else if (ch
== '\n') ...' aren't too bad).

Barry Margolin
Thinking Machines Corp.

barmar@think.com
uunet!think!barmar

edw@IUS1.CS.CMU.EDU (Eddie Wyatt) (03/23/88)

> >  I think he meant in terms of expressability.  Which is true in a
> >practical sense, false in theorectically.  'switch' and 'else-if'
> >are equivalent in terms of expressability.
> 
> That is obviously untrue.  A switch can only check one expression, and
> can only compare it against constant values.  An else-if chain can
> check for multiple conditions and can compare computed values.  You
> can even have side effects in the conditional expressions, which can
> affect later conditionals (I don't recommend this practice taken to
> extreme, but things like 'if ((ch = getchar()) == EOF) ... else if (ch
> == '\n') ...' aren't too bad).

Outline of the prove that switch could replace an else-if chain.  The
opposite direction is trivial and is left for the ready to prove :-)


Actually one needs to do an induction proof, but this is just an outline.


	if (cond1) 	expr1
	else if (cond2) expr2
	else if (cond3) expr3
	....
	else if (condN) exprN

is equivalently rewritten as:

	if (cond1) expr1
	else 
	    { 
	    if (cond2)	expr2
	    else	
		{
		if (cond3) expr3
		else
		    {
		    .......
		    }
		}
	    }

forever pair of if (cond) expr1 else expr2 rewrite the statement as:

	switch(cond)
	    {
	    case TRUE  :	expr1; break;
	    case FALSE :	expr2; break;
	    }

so the else-if chain becomes:

	switch(cond1)
	    {
	    case TRUE  : 	expr1; break;
	    case FALSE :
		switch (cond2)
		    {
		    case TRUE :		expr2; break;
		    case FALSE :	......
		    }
		break;
	    }


Remember what I said, in a practical sense, yes one needs else-ifs but
in a theorical sense, you gain nothing in terms of the expressable functions.
-- 

Eddie Wyatt 				e-mail: edw@ius1.cs.cmu.edu

richard@aiva.ed.ac.uk (Richard Tobin) (03/23/88)

In article <25835@cca.CCA.COM> g-rh@CCA.CCA.COM.UUCP (Richard Harter) writes:
>	Theoretically the switch construct is more powerful than an else-if
>chain because it selects in one step.  Execution is O(1) rather than O(n)
>where n is the number of branches.

No, *practically* the switch construct is more powerful because it selects
in one step.  *Theoretically* switch can compile into an if-then-else chain
(and often will, if the case values are widely separated), and an
if-then-else chain can be analysed to discover whether it can be compiled
into a jump table.

The theoretical power of a language is [determined by] the set of
algorithms it can implement.  Its efficiency in executing these algorithms
depends on its implementation, though it's obviously easier to produce
efficient implementations of some languages than others.
-- 
Richard Tobin,                         JANET: R.Tobin@uk.ac.ed             
AI Applications Institute,             ARPA:  R.Tobin%uk.ac.ed@nss.cs.ucl.ac.uk
Edinburgh University.                  UUCP:  ...!ukc!ed.ac.uk!R.Tobin

g-rh@cca.CCA.COM (Richard Harter) (03/23/88)

In article <18377@think.UUCP> barmar@fafnir.think.com.UUCP (Barry Margolin) writes:

>> > 	Theoretically the switch construct is more powerful than an else-if
>> > chain because it selects in one step.  Execution is O(1) rather than O(n)
>> > where n is the number of branches.  It is, for example, considerably more
>> > efficient to use a switch with a thousand cases than an else-if chain with
>> > a thousand elses.  [This is not to be taken as an endorsement of such
>> > code. :-)]
>>  I think he meant in terms of expressability.  Which is true in a
>>practical sense, false in theorectically.  'switch' and 'else-if'
>>are equivalent in terms of expressability.

>That is obviously untrue.  A switch can only check one expression, and
>can only compare it against constant values.  An else-if chain can
>check for multiple conditions and can compare computed values.  You
>can even have side effects in the conditional expressions, which can
>affect later conditionals (I don't recommend this practice taken to
>extreme, but things like 'if ((ch = getchar()) == EOF) ... else if (ch
>== '\n') ...' aren't too bad).

	We're talking apples, oranges, and pears, here.  The switch
construct is superior to the else-if chain in computational efficiency,
in the sense that it can select between n alternatives in one step
rather than n steps, when the selection is made on the value of a
variable which has a range of length n.  An else-if chain can be
replaced by successive switches or by a single switch contained within
a while loop (in C with no additional if's), e.g.

	more = 1;
	kludge = 0;
	while (more) switch(kludge) {
	  case 0: kludge += (1 + (expr1));
	          break;
	  case 1: kludge = 3;
                  break;
	  case 2: action1;
	          more = false;
	          break;
	  case 3: kludge += (1 + (expr2));
	  ....

[This code does not have the good programming stamp of approval.]  If
we ignore questions of computational power and simply consider expressive-
ness, else-if chains can obviously replace switches.  And, of course,
if we program reasonably, we use each where it is appropriate.  (Easier
said then done.)
-- 

In the fields of Hell where the grass grows high
Are the graves of dreams allowed to die.
	Richard Harter, SMDS  Inc.

dwh@twg-ap.UUCP (Dave Hamaker) (03/24/88)

In article <941@micomvax.UUCP>, ray@micomvax.UUCP (Ray Dunn) writes:
> Case statements should just be generalised into a "shorthand" way of
> stating if...elseif...elseif...else...

It seems to me that Ray's proposal doesn't go far enough; it's hard to accept
a construct which is just another way of writing an if...elseif...else...
(why clutter a language with two ways to say the same thing?).  Yet I also
feel that case constructs are always artificially so constrained that they
violate my sense of language asthetics.

In thinking about this a lot, I came up with the notion that the switch
expression and case labels can be viewed as textual pieces of a boolean
expression whose truth value chooses the specific case label.  You get
something like:

    switch [(expression)] {
        case expression1:
            ...
        case expression2:
            ...
        default:
            ...
    }

where the square brackets indicate that "(expression)" is optional.  The
switch expression is the "left-hand-side" of the boolean expression and
expression1/expression2 are the "right-hand-side."  As such these are not
true expressions; they are really substrings of a constructed expression,
although they *may* be complete expressions.  They can't be completely
arbitrary substrings, though; they must be parentheses-balanced because
the switch expression is enclosed in parenthesis, and delimited chars and
strings must be complete for a similar reason (how else do you distinguish
the closing parenthesis from a parenthesis contained in the constant?).

In compiling the construct, you need the ability to determine if expression,
expression1 and expression2 are "complete" or not.  This is just a matter
of whether you can parse them completely.  If the switch expression and
a case expression are both complete, then you presume a == operator goes
between them.  Otherwise, you just concatenate them.  You choose the first
true case, if there is any ambiguity, which amounts to qualifying each case
with the negations of all previous ones (the default label is the negation
of all the cases).

Thus, you get things like:

    switch (i) {
        case (<0):
            ...
            break;
        case (0):
            ...
            break;
        case (>0):
            ...
            break;
    }

or
        
    switch {
        case (i<0):
            ...
            break;
        case (i==0):
            ...
            break;
        case (i>0):
            ...
            break;
    }

or even
        
    switch (i==j) {
        case ([0]):
            ...
            break;
        case ([1]):
            ...
            break;
        case ([2]):
            ...
            break;
    }

(which is admittedly pretty weird but only an illustration).

If we require complete switch expressions to be evaluated only once, then
the current switch syntax is completely contained, and it can be determined
if the old conditions are met (and you can compile code which is just as
efficient or inefficient as you used to).  I wouldn't guarantee any order
of case expression evaluation nor that all case expressions would actually
be evaluated (lazy evaluation is allowed); otherwise expression evaluation
side effects can get in the way of optimization.

-Dave Hamaker
The Wollongong Group
...!sun!amdahl!twg-ap!dwh

g-rh@cca.CCA.COM (Richard Harter) (03/24/88)

>No, *practically* the switch construct is more powerful because it selects
>in one step.  *Theoretically* switch can compile into an if-then-else chain
>(and often will, if the case values are widely separated), and an
>if-then-else chain can be analysed to discover whether it can be compiled
>into a jump table.

>The theoretical power of a language is [determined by] the set of
>algorithms it can implement.  Its efficiency in executing these algorithms
>depends on its implementation, though it's obviously easier to produce
>efficient implementations of some languages than others.

This is a useless definition of the power of a language.  Almost all
languages of any interest can implement a turing machine emulation in
which all effective algorithms can be implemented.  You can even do it
if the only flow-control construct is the 'while' loop.

You can pick any measure of strength of constructs that you like --
however the usual measure is space and time efficiency.  For example, it
can be shown, that if language A provides the if-then-else and the while-loop
and language B provides the if-then-else and the goto as their sole flow
control constructs, then there are programs of length N and execution time
O(N) in language B such that any equivalent program in language A requires
either O(N logN) execution time or O(n *exp(N)) space, or some combination
thereof.  By this criteria the goto is more powerful than the while-loop.
The hierarchy of strengths runs

	while-loop
	forever-loop-with-multiple-escapes
	goto
	procedure-invocation-and-return
	computed-goto/switch

The switch construct is equivalent in power to the computed goto.

This measure of power is unambiguous.  One can also speak of the expressiveness
of a construct, in the sense of what can be easily done in the language.
Although this is probably the most useful criteria (and I suspect what you
really) meant it is obviously vague and subjective.
-- 

In the fields of Hell where the grass grows high
Are the graves of dreams allowed to die.
	Richard Harter, SMDS  Inc.

jrl@anuck.UUCP (j.r.lupien) (03/25/88)

From article <18377@think.UUCP>, by barmar@think.COM (Barry Margolin):
> In article <1186@PT.CS.CMU.EDU> edw@IUS1.CS.CMU.EDU (Eddie Wyatt) writes:
>> >>Yes, in a sense switch is less powerful than an else-if chain.  Let's keep it,
>> >>for the same reasons that we retain flow constructs less powerful than goto.
>> > 	Theoretically the switch construct is more powerful than an else-if
>> > chain because it selects in one step.  Execution is O(1) rather than O(n)
>> > where n is the number of branches. 

  Things get even better than this. It is possible to define parameters
of switch selection which allow even more optimization. These parameters
involve the number of switch values and the range of values. For any
given machine architecture, certain ranges of these parameters will
perform better using one of: indexed dispatch table, linear search
dispatch table, and hashed dispatch table. The compiler can count
the number of entries, examine the spread, and select the best 
implementation for each switch. 
   Naturally "best" means slightly different things to different 
programmers: some need speed, some need space, some need some arcane
relationship between speed and space. Building in a means for the 
programmer to tell the compiler which feature is more important and
by how much would permit the compiler to make even better choices
for such cases as switch coding. (for instance, MSC 5.0 has 
optimization switches to allow selection of speed or space optimazation)

John R. Lupien
twitch!mvuxa!anuxh!jrl