#1819 switch and case

detroitmatt Wed 7 Mar 2012

I've had an idea kicking around my head for a while regarding the switch and case. I was wondering what this community thought of them.

Switch and Case:

switch(a) changes the implicit parameter to a until the end of the block. this continues to point to the Object we're performing on.

The case expression is a special variant of the if expression. case a { } is shorthand for if( <the implicit parameter> == a) { }. else can precede a case just as it can an if. case !a { } is shorthand for if( <the implicit parameter> != a) { }.

Therefore, switches and cases make natural companions, but can be used independantly of each other (Although this isn't the intended advantage of this construct).

Bool IAmIn(Obj[] arr)
{
   arr.each |x| {
      case x { return true } // if this == x
   }
}
static Void conditionalProcess(FooBar fb, Bool b := false)
{
   if(b) {
      switch(fb) { //We're now operating on fb
         foo()
         bar()
      }
   }
   else {
      switch(fb) {
         bar()
         foo()
      }
   }
}

For code this simple, the new case and switch constructs don't improve the code much, but the returns expand as the complexity of the code does.

Switches and Cases also come in a multi-dimensional flavor

switch(a, b) { //There can be any number of arguments here
   case (1, 2) { foo(); }
   else case (2, 3) { bar(); }
   else case (!2, 3) { baz(); }
}

is shorthand for

if(a==1 && b == 2) { foo(); }
else if(a==2 && b==3) { bar(); }
else if(a!=2 && b==3) { baz(); }

I haven't looked much at the internals of Fan, so I don't know how hard this would be to implement. Regardless of that, is this a good idea?

dobesv Thu 8 Mar 2012

So, the first proposal would be to translate a free-standing case statement into an if(it == _). Seems potentially interesting but doesn't save that much typing ... did you have other more compelling use cases in mind?

The second is that when you use switch(x) you can make methods calls on x without writing x. in front. This is actually already available using "it-blocks". Just write:

x {
  foo // Calls x.foo() if available
  bar // Calls x.bar() if available
}

For multi-dimensional switch, I'm pretty sure if you just put []'s around it you can get a similar effect although it won't support negation:

switch([a,b]) { // I think this should work, but I haven't tried it yet
  case [1,2] : foo
  case [2,3] : bar
  default: if(a!=2) baz
}

detroitmatt Thu 8 Mar 2012

I completely forgot about it-blocks. I'm still pretty new to the language.

The biggest advantage I saw to separating switches and cases was that you could now include non-conditional code within a switch statement, and makes the switch less of a specialized block, and more of a way of temporarily changing subjects, that code can be executed normally in, like a loop or if statement.

switch(a) {
   foo()
   bar()
   case b {
      baz()
   }
   case c {
      bat()
   }

The biggest reason I like the case shorthand is because a huge chain of if-else statements (used in this way) always look very ugly to me: The value you were testing for was stuck in the middle of the line, in a very dense region, if you used one-line style else. compare

if( a == b ) {
   //Here we have some code
   //It has to be placed here
   //But it blends into the next elif
   //and makes the condition not stand out
} else if(a == c) {
   //As you can see,
   //the condition is
   //a little bit lost
   //within the blocks of code.
   //it's not hard to find if you look
   //but it doesn't pop out
} else if(a == d) {
   //The problem only gets
   //worse and worse as the number
   //of elifs increase
}

with

switch(a) {
   case b {
      //here we have the same
      //code (I'm keeping the lines
      //about the same length and number)
      //so that the comparison is fair
   } case c {
      //notice now
      //how the comparison is
      //much clearer. the purpose
      //of the case wasn't to reduce
      //keystrokes (although it does)
      //it was mostly to provide a stronger
   } case d {
      //visual structure and layout for the
      //repetitive identity check.
   }
}

With many case statements, we no longer have to check to make sure all the ifs compare simple identity, and compare it properly. The use of a switch-case informs the one reading the code that we're testing very simple things, and possibly a lot of them. In addition, the conditional statements blend into the surrounding code a lot less.

Let's say I'm writing a simple console-based blackjack game

Int p := scoreHand(player)
Int d := scoreHand(dealer)
switch(p,d) {
   case 21,21 {
      echo("What are the odds!")
      return TIE
   }
   case 21,!21 {
      echo("Wow, that was quick!")
      return PLAYER_WINS
   }
   case !21,21 {
      echo("Bad luck, chief!")
      return DEALER_WINS
   }
}

Int p := scoreHand(player)
Int d := scoreHand(dealer)
if( p == 21 && d == 21) {
   echo("What are the odds!")
   return TIE
} else if( p == 21 && d != 21) {
   echo("Wow, that was quick!")
   return PLAYER_WINS
} else if( p != 21 && d == 21) {
   echo("Bad luck, chief!")
   return DEALER_WINS
}

Maybe it's just me, but I think the elifs are ugly and hard to read.

That said, after being reminded of it-blocks and realizing the List approach, consider this idea retracted, unless there's something else I failed to realize that makes it useful :P

SlimerDude Fri 9 Mar 2012

The multi-switch sounds like a nice idea, but I personally have never had a use case for it. With regard to your example above, to make the comparison stand out and more readable I'd probably refactor the comparisons out into a seperate class:

result = Results() { d=scoreHand(dealer); p=scoreHand(player) }

if (result.isTie) {
  echo("What are the odds!")
  return TIE
}

if(result.pWins) {
  echo("Wow, that was quick!")
  return PLAYER_WINS
}

if( result.dWins) {
  echo("Bad luck, chief!")
  return DEALER_WINS
}

class Results {
  Int d; Int p;
  // stick all confusing boolean logic in here
  Bool isTie() { d == 21 && p == 21 } 
  Bool pWins() { d != 21 && p == 21 }
  Bool dWins() { d == 21 && p != 21 }
}

Plus if you often make heavy use of switches, there may be a case for using inheritance. A quick google gives me this example: Replace Conditional with Polymorphism

detroitmatt Fri 9 Mar 2012

The Results class there is really only appropriate as an inner class, which Fantom doesn't have. A neat solution otherwise though.

Login or Signup to reply.