Selection

Two-way conditionals:

Format:

if (condition) consequent else alternative

Example:

if (x < y) max = x else max = y

One-way conditionals:

The alternative is optional:

if (!valid(input)) throw new Exception("invlid input")

Conditional expressions versus conditional commands

There's a difference between conditional expressions and conditional commands. A conditional expressions returns a value, while a conditional command produces some side effect such as I/O or updating some variables.

In Java (and C) the conditional expression isn't used as much as the conditional command:

if (x < y) max = x else max = y // conditional command

max = (x < y)? x: y  // conditional expression

Scala, on the other hand, only provides a conditional expression, but uses the syntax of a conditional command:

max = if (x < y) x else y // conditional expression

Of course it also works with commands:

if (x < y) max = x else max = y

Multi-Way Selections

Multi-way selections can be achieved by nesting two-way selections:

if (income < 0)
   System.err.println("income must be non-negative");
else if (income < 10000)
   tax = 0;
else if (income < 20000)
   tax = .2 * income;
else if (income < 35000)
   tax = .3 * income;
else if (income < 50000)
   tax = .5 * income;
else
   tax = .75 * income;

 

Beware of the dangling else.

Question: How much tax does a person making $15,000 pay? (Answer = unknown.) $8000?  (Answer = $800.)

if (income < 10000)
if (5000 < income)
tax = .1 * income;
else tax = .2 * income;

Switch

Both Java and C++ inherit the bizarre switch command from C. The syntax and semantics of the switch command are so awkward that they almost render the command unusable. Here is the syntax:

switch(EXPRESSION)
{
   case EXPRESSION: COMMANDS
   case EXPRESSION: COMMANDS
   case EXPRESSION: COMMANDS
   // etc.
   default: COMMANDS
}

The expression in parenthesis, called the key, must produce an integer or integer subtype value when executed. The switch command contains one or more case clauses. Each case clause begins with an expression called a guard, followed by zero or more commands, called consequences. The last case clause traditionally begins with the reserved word "default". The list of commands following it are called alternatives:

switch(KEY)
{
   case GUARDCONSEQUENCES
   case GUARDCONSEQUENCES
   case GUARDCONSEQUENCES
   // etc.
   default: ALTERNATIVES
}

Semantically, the key is evaluated. The value of the key is compared to the value of each guard, starting with the topmost guard. When a match is found, all commands following the guard, including commands that follow subsequent guards, are executed. The defaule guard matches all keys. For example, here is a flowchart describing the semantics of a switch command with three case clauses guarded by expressions called GUARD1, GUARD2, and GUARD3, respectively:

image003

Notice that if KEY == GUARD1 is false, but KEY == GUARD2 is true (i.e., not 0 or false), then CONSEQUENCES1 will be ignored, but CONSEQUENCES2 and CONSEQUENCES3 will be executed. This happens even if KEY == GUARD3 is false!

To prevent the consequences of subsequent case clauses from being executed, programmers often end each sequence of consequences with a break command. Break commands will be discussed later. Essentially, a break command causes control to exit the current control command and pass to the next command.

Here's an example of a switch command:

switch(op)
{
   case '+': result = arg1 + arg2; break;
   case '*': result = arg1 * arg2; break;
   case '-': result = arg1 - arg2; break;
   case '%': result = arg1 % arg2; break;
   case '/': 
      if (arg2) result = arg1 / arg2; 
      else System.err.println("can't divide by 0");
      break;
   default: System.err.println("unrecognized operator " + op);
}

We can only use this switch command if op happens to be declared as a character, since char is a subtype of int:

double arg1, arg2, result;
char op; // = operator used to combine arg1 & arg2

If we declare op as a string:

string op;

then we must use nested if commands.

Scala's Match

Scala's match is a powerful improvement to switch.

Format:

key match {
   case guard1 => branch1
   case guard2 => branch2
   case guard3 => branch3
   // etc
   case _ => default
}

Matching values:

var day = 3
day match {
   case 0 => println("Sunday")
   case 1 => println("Monday")
   case 2 => println("Tueaday")
   case 3 => println("Wednesday")
   case 4 => println("Thursday")
   case 5 => println("Friday")
   case 6 => println("Saturday")
   case _ => throw new Exception("Invalid day")
}

day match {
   case 0 | 1 => println("Weekend")
   case 2 | 3 | 4 | 5 => println("Weekday")
}

Matching types:

var shape: Shape = new Triangle(20, 10)
shape match {
   case shape: Triangle => println(shape.area)
   case shape: Rectangle => println(shape.area)
   case _ => throw new Exception("Invalid shape")
}

 

try { ... }
catch {
   case e: NegativeAmountException => println("amount must be positive")
   case e: InsufficientFundsException => println("Insufficient funds")
   case e: BadCommandException => println("Invalid command, type help")
   case _: Throwable => println("unknown error")
}

Matching Conditions

income match {
   case income if income < 100 => .1 * income
   case income if income < 1000 => .2 * income
   case income if income < 10000 => .3 * income
   case _ => .1 * income
}

Matching patterns

We can use a regular expression to extract matching elements from a string. (More on this later.)

val expPattern = """([0-9]+)\s*(\+|\*|-|/)\s*([0-9]+)""".r

def eval(exp: String) = 
    exp match {
       case expPattern(arg1, "+", arg2) => arg1.toInt + arg2.toInt
       case expPattern(arg1, "-", arg2) => arg1.toInt - arg2.toInt
       case expPattern(arg1, "*", arg2) => arg1.toInt * arg2.toInt
       case expPattern(arg1, "/", arg2) => arg1.toInt / arg2.toInt
       case _ => throw new Exception("Invalid expression")
    }