Switch from switch

home

 

Switch statements at first seem like a wonderful thing, a clean way to compare a variable to multiple values. Here's a pascal-style switch:

// pascal-style switch. looks very nice:
case n of:
  1: w="one";
  2: w="two";
  3: w="three";
  4,5,6: w="several";
  7..12: w="many";
  otherwise: w="too many";
end;

What a joy! We're happy about only needing to write variable n once. And the values for comparrison are lined-up nicely. It feels like a fundamental language feature -- compare a variable to several things.

If we had to criticize it, gun to our heads, we could say it has too many special rules. The leading case is fine -- every command needs 1 keyword. But the colons are new and special just for this, as is the final otherwise, and the 4,5,6 and 7..12 range syntaxes.

It even gets a little worse. Here's a C switch statement:

// C-style. Not quite as nice:
switch(n) {
  case 1: w="one";
     break;
  case 2: w="two";
     break;
   default: w="too many";
}

This is the more common form. It's got an extra case before every line, plus the clutter of break at the end. break is interesting. Originally we thought being able to fall-through would be great. Suppose 1 and 2 do A(), and 2 also does B(). We can move things around so we need only call A() once:

// theoretical use of a break statement:
switch(n) {
  case 2:
     B();
     // no break, keep running the lines below
  case 1:
     A(); // 2 and 1 each do this
     break; // done with cases 1 and 2
  case 3: ...

Yuck. I love avoiding code duplication as much as the next guy, but that's ugly. And we were forced to run B() first. And it can't handle situations where multiple cases overlap, suppose: A() and B(), B() and C(), and just C(). The whole fall-through idea is just no good; more modern switch's scrap it. For weird stuff like that, IF statements will do:

// overlapping effects: 1 does A(); 2 does A() and B(); 3 does B() and C():
if(n==1 || n==2) A();
if(n==2 || n==3) B();
if(n==3) C();

In the early days of computing, IF's were messy. At first we didn't have an ELSE. You worked around that with careful programming. Later we got an ELSE but we weren't allowed to have a second IF in there. For that, we needed a special ELIF (or sometimes ELSEIF). It was a mess:

if a=1: q="one";
else q="not one";

// if you want a 2nd IF, you can't use an else. You must use the special ELIF:
if b==1: w="one";
elif b==2: w="two";
else w="many";

It was years before we got a simple IF with an optional ELSE, and that was all we needed. For example, to make a cascading-if that does the same thing as a switch:

if(n==1) w="one";
else if(n==2) w="two";
else if(n<=6) w="several";
else if(n<=12) w="many";
else w="too many";

Compared to a switch the test's don't pop out quite as much, and we need to repeat n in each one. But we don't need a special "none of the above", and we can check ranges in a nicer way. switch statements may have seemed nice compared to the old primitive IF's, but now they're about even.

But when we make a map of everything to learn, switch statements have no marginal gain. We need to learn IF's, no matter what. Cascading IF's are an obvious next step. We'll probably want to learn value returning if's: w=(n==1?"one":n==2?"two":"several");. Once we learn those two, we don't get anything new from a switch.

Further more, there's often a better way. switch's and cascading IF's are the same process, and it's often not the one we want. Many look-ups should use an array:

// this replaces an entire switch:
string[] ColorNames = {"red","blue","green","orange","puce"};
string w = ColorNames[n];

It can sometimes be nice to group by effect rather than cause. Here, the 4 modes control whether we divide by Interval, Mass, both, or none. That's broken into two questions:

// this replaces a switch with 4 cases (included the do-nothing case):
if(mode==continuous || mode==continuousBiased) d*=1/interval;
if(mode==continuousBiased || mode==instantBiased) d*=1/mass; 

If each item has several properties, not completely independent, nested checks can be better. Here each main power type has a typical voltage:

if({toaster,radio,fan}.contains(n)) { // cutesy pseudocode
  powerType="AC";
  volts=240; // default voltage for AC
  if(n==fan) volts=220;
}
else if({generator,zapper}.contains(n) {
  powerType="DC";
  volts=180;
  ...
}

Nested switch statements can also do this, or IF's inside of a switch, but nested IF's feels the most natural.

We might be be working with a framework with a nice way to enter and serialize lists. Entering info there is better than hard-coding it into a switch. Once again, we have an easy array look-up:

class Planet_t { string name; float mass; int fromTheSun; }
Planet_t[] Planets; // entered externally and serialized

// the "switch" statement to find planet n's name:
w=Planets[n].name;

Often what we really wanted was a formula. Suppose a game has increasing difficulty, with a switch assigning 1, 1, 1, 2, 2, 3 ... for each level. Using 1+floor(level/3) is short and probably gives satisfactory results. It's easier to adjust.

Some languages extend the basic discrete-item switch to work with ranges like 10..25. Once again, IF's are as good or better at that. But either way, a list of breakpoints and a loop is better. It's the same idea as a spreadsheet VTable:

// the input is breakpoints between intervals. Output in the interval:
int getInterval(int[] Breakpoints, int n) {
 for(int i=0; i<BreakPoints.Length; i++)
   if(n<=BreakPoints[i]) return i;
 return BreakPoints.Length; // n is past the largest entry
}

int sizeCatIndex=getInterval( {2,5,10,25}, n); // < the intervals
w={"tiny","small","medium","big","huge"}[sizeCatIndex]; // < the results

Swift, and other modern scripting-style languages, gives switch's an extensive where clause. That makes them much more flexible. But IF's are still better:

// as a switch:
switch p2 { // p2 is a point 2-tuple
  case (let x, let y) where (x>=0 && y>=0): w="quadrant one";
  case (let x, let y) where (x>=0 && y<0): w="quadrant four";
  ...

// as an if:
int x=p2.x, y=p2.y;
if(x>=0 && y>=0) w="quadrant one";
if(x>=0 && y<0) w="quadrant two";

To sum up: in every case, switch is no good. There's usually a better way than a hard-coded "one means this, two means that". And even if there isn't, a cascading IF works fine.

I think people enjoy switch's so much because of all the funny syntax. Programmers like talking about Turing Complete -- how a programming language only needs a few commands; you can compute PI using World of Warcraft macros; and so on. But at the same time, we love our fluff. It's so fun to mess with do-while and repeat-until loops. switch is irresistible because of case and break and default, and 1,2,3:.

Without exaggeration, I can say the switch statement represents everything wrong with programming today. Yeah, yeah, it's a command in the language. Well, if there was a command to make your program jump over a cliff, would you use that?

 

 

Comments. or email adminATtaxesforcatses.com