Switch from switch

home

 

If you don't know any better, switch statements at first seem like a wonderful thing -- a clean way to compare one variable to multiple values. An example, here's a pascal-style switch which looks up "number words":

// 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! Variable n only occurs once, and the values for comparison are the first thing in each line. If we had to criticize it, a gun to our head, we could say it has too many special rules: colons after the values, the otherwise at the end, and two special rules for multiples (4,5,6 and 7..12).

Part of why switch-statements seemed so nice was that IF's were so clunky. In the early days of computing we didn't have ELSE's. When we got them, a funny rule prohibited an IF after an ELSE -- you needed to use a special ELIF for that. Blocks were marked off by THEN's and ENDIF's. But now we've got lovely cascading IF's. The switch above looks fine rewritten in that style:

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";

Meanwhile, switch statements acquired some junk. This C switch statement requires an extra case and break with each item:

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

The purpose of break is a fun sidetrack. Without a break statement, a case "falls through", running code for all cases below it. In theory this allowed us to share code. An example: case-1 prints A, and also B:

// a switch where case-1 falls through to case-2:
switch(n) {
  case 1: print("A");
     // no break, n=1 keeps running lines below
  case 2: print("B");
     break; // done with cases 1 and 2
  case 3: print("C");

Despite seeming pretty cool, in practice this trick is awkward, limited, and pretty much never used. Newer languages, such as Swift, have gotten rid of the break's (and the fall-through). But they keep the clutter of case's.

One problem with switch's is they have no marginal gain. When we learn programming, we'll need to learn IF's, and all of their tricks. After that, a switch is redundant. But the real problem is that the thing they do is bad. We shouldn't be hardcoding values into our code, and that's all a switch statement does.

A common use for a switch (or a cascading IF) is an indexed look-up. We can do that more directly by losing the switch and instead indexing into a real array:

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

Some frameworks even have a nice way to serialize lists, making the array trick even nicer. Below, Unity3D will auto-load ColorNames:

public string[] ColorNames; // All we need. These are entered elsewhere and serialized.

  string w = ColorNames[n]; // the "switch" statement, as an array look-up

It's common to have a switch-case be a single value or short list, but some languages have a syntax for ranges: case 10..25:. That allows us to make "breakpoint tables" (Ex: 0-10, 11-15, 16-24, 25+). Again, a cascading IF is just fine. But we can do better than either with an array of breakpoints:

// set-up 5 categories of cat-sizes and the ranges determining them:
string[] CatSizeWords = {"tiny","small","medium","big","huge"};
int[] CatSizeBreakPoints = {2,5,10,25};
// ex: 0-2 is "tiny", 2-5 is "small" ... 25+ is "huge"

// use it like this:
int ii=getInterval(CatSizeBreakPoints , n); // if n is 8, returns 2 (signifying the 5-10 range)
string w=CatSizeWords[ii];

// the function to locate the range, given an array of breakpoints and a value:
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
}

Defining CatSizeBreakPoints is shorter than writing out a switch. And a more deluxe array type would allow us to add or remove breakpoints on the fly. We could even convert it into a class.

Often what we really wanted was a formula. Suppose a game has increasing difficulty for every 3 levels. We could use a switch statement where we type out 1,1,1,2,2,2,3,3,3 ... . But the formula 1+floorLevel/3 is easier to read and can be changed more easily (assuming we keep it as a pattern).

Even when we need to do things case-by-case, switch's tend to make us think of it one way, which may not be the best. Sometimes it's nice to group by action rather than cause. Here, the possible actions are dividing by interval and dividing by mass. There are 4 cases (neither, either, both) but only 2 IF's:

// each "thing we might do" gets an IF
// 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; 

Sometimes the items have a relationship which is easier to see with nested checks. Here each power type is grouped and given 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,car}.contains(n) {
  powerType="DC";
  volts=180; // default volts for DC
  ...
}

Sure sure, you can nest switch-statements this same way (or have IF's in a switch and so on). But having a switch statement encourages simply listing every item, 1-by-1.

Swift, and other modern scripting-style languages add an extensive where clause. This assumes p2 is a 2-part object. Each case checks each part (let x creates a temporary local variable):

// check an (x,y) for its quadrant, using 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";
  ...

That's fun. But IF's can do the same thing with about the same level of elegance and no special rules:

// 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";

I think all of the extra rules are why switch statements survive. In a manual, switch syntax takes up pages -- much more than the few rules for IF's. They must be important. And many people prefer memorizing rules when the alternative is recombining old ones. To a certain type of person, it feels better to use a specialized (let x, let y) where than a plain old IF.

To sum up, switch statements are a relic of the old days, along with do-while and repeat-until. We don't need a special feature for hard-coding a table look-up. I mean, if there was a command to make your program jump over a cliff, would you do that?

 

 

Comments. or email adminATtaxesforcatses.com