## 15 Case Classes and Pattern Matching

Sample run of chapter's interpreter examples

### 15.1 A simple example

```
abstract class Expr
case class Var(name: String) extends Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr) extends Expr
case class BinOp(operator: String,
left: Expr, right: Expr) extends Expr

scala> val v = Var("x")
v: Var = Var(x)

scala> val op = BinOp("+", Number(1), v)
op: BinOp = BinOp(+,Number(1.0),Var(x))

scala> v.name
res0: String = x

scala> op.left
res1: Expr = Number(1.0)

scala> println(op)
BinOp(+,Number(1.0),Var(x))

scala> op.right == Var("x")
res3: Boolean = true

UnOp("-", UnOp("-", e))  => e   // Double negation
BinOp("+", e, Number(0)) => e   // Adding zero
BinOp("*", e, Number(1)) => e   // Multiplying by one

scala> simplifyTop(UnOp("-", UnOp("-", Var("x"))))
res4: Expr = Var(x)

def simplifyTop(expr: Expr): Expr = expr match {
case UnOp("-", UnOp("-", e))  => e   // Double negation
case BinOp("+", e, Number(0)) => e   // Adding zero
case BinOp("*", e, Number(1)) => e   // Multiplying by one
case _ => expr
}

selector match { alternatives }

switch (selector) { alternatives }

UnOp("-", UnOp("-", e))

// In file pattern-matching/Misc.scala

expr match {
case BinOp(op, left, right) =>
println(expr +" is a binary operation")
case _ =>
}

```

### 15.2 Kinds of patterns

```
// In file pattern-matching/Misc.scala

expr match {
case BinOp(op, left, right) =>
println(expr +"is a binary operation")
case _ =>
}

// In file pattern-matching/Misc.scala

expr match {
case BinOp(_, _, _) => println(expr +"is a binary operation")
case _ => println("It's something else")
}

// In file pattern-matching/Misc.scala

def describe(x: Any) = x match {
case 5 => "five"
case true => "truth"
case "hello" => "hi!"
case Nil => "the empty list"
case _ => "something else"
}

scala> describe(5)
res5: java.lang.String = five

scala> describe(true)
res6: java.lang.String = truth

scala> describe("hello")
res7: java.lang.String = hi!

scala> describe(Nil)
res8: java.lang.String = the empty list

scala> describe(List(1,2,3))
res9: java.lang.String = something else

// In file pattern-matching/Misc.scala

expr match {
case 0 => "zero"
case somethingElse => "not zero: "+ somethingElse
}

scala> import Math.{E, Pi}
import Math.{E, Pi}

scala> E match {
|   case Pi => "strange math? Pi = "+ Pi
|   case _ => "OK"
| }
res10: java.lang.String = OK

scala> val pi = Math.Pi
pi: Double = 3.141592653589793

scala> E match {
|   case pi => "strange math? Pi = "+ pi
| }
res11: java.lang.String = strange math? Pi = 2.7182818...

scala> E match {
|   case pi => "strange math? Pi = "+ pi
|   case _ => "OK"
| }
<console>:9: error: unreachable code
case _ => "OK"
^

scala> E match {
|   case `pi` => "strange math? Pi = "+ pi
|   case _ => "OK"
| }
res13: java.lang.String = OK

// In file pattern-matching/Misc.scala

expr match {
case BinOp("+", e, Number(0)) => println("a deep match")
case _ =>
}

// In file pattern-matching/Misc.scala

expr match {
case List(0, _, _) => println("found it")
case _ =>
}

// In file pattern-matching/Misc.scala

expr match {
case List(0, _*) => println("found it")
case _ =>
}

def tupleDemo(expr: Any) =
expr match {
case (a, b, c)  =>  println("matched "+ a + b + c)
case _ =>
}

scala> tupleDemo(("a ", 3, "-tuple"))
matched a 3-tuple

def generalSize(x: Any) = x match {
case s: String => s.length
case m: Map[_, _] => m.size
case _ => -1
}

scala> generalSize("abc")
res14: Int = 3

scala> generalSize(Map(1 -> 'a', 2 -> 'b'))
res15: Int = 2

scala> generalSize(Math.Pi)
res16: Int = -1

// In file pattern-matching/Misc.scala

expr.isInstanceOf[String]

// In file pattern-matching/Misc.scala

expr.asInstanceOf[String]

if (x.isInstanceOf[String]) {
val s = x.asInstanceOf[String]
s.length
} else ...

scala> def isIntIntMap(x: Any) = x match {
|   case m: Map[Int, Int] => true
|   case _ => false
| }
warning: there were unchecked warnings; re-run with
-unchecked for details
isIntIntMap: (Any)Boolean

scala> :quit
\$ scala -unchecked
Welcome to Scala version 2.7.2
(Java HotSpot(TM) Client VM, Java 1.5.0_13).
Type in expressions to have them evaluated.

scala>  def isIntIntMap(x: Any) = x match {
|   case m: Map[Int, Int] => true
|   case _ => false
| }
<console>:5: warning: non variable type-argument Int in
type pattern is unchecked since it is eliminated by erasure
case m: Map[Int, Int] => true
^

scala> isIntIntMap(Map(1 -> 1))
res17: Boolean = true

scala> isIntIntMap(Map("abc" -> "abc"))
res18: Boolean = true

scala> def isStringArray(x: Any) = x match {
|   case a: Array[String] => "yes"
|   case _ => "no"
| }
isStringArray: (Any)java.lang.String

scala> val as = Array("abc")
as: Array[java.lang.String] = Array(abc)

scala> isStringArray(as)
res19: java.lang.String = yes

scala> val ai = Array(1, 2, 3)
ai: Array[Int] = Array(1, 2, 3)

scala> isStringArray(ai)
res20: java.lang.String = no

// In file pattern-matching/Misc.scala

expr match {
case UnOp("abs", e @ UnOp("abs", _)) => e
case _ =>
}

```

### 15.3 Pattern guards

```
BinOp("+", Var("x"), Var("x"))

BinOp("*", Var("x"), Number(2))

scala> def simplifyAdd(e: Expr) = e match {
|   case BinOp("+", x, x) => BinOp("*", x, Number(2))
|   case _ => e
| }
<console>:10: error: x is already defined as value x
case BinOp("+", x, x) => BinOp("*", x, Number(2))
^

scala> def simplifyAdd(e: Expr) = e match {
|   case BinOp("+", x, y) if x == y =>
|     BinOp("*", x, Number(2))
|   case _ => e
| }

// match only positive integers
case n: Int if 0 < n => ...

// match only strings starting with the letter `a'
case s: String if s(0) == 'a' => ...

```

### 15.4 Pattern overlaps

```
def simplifyAll(expr: Expr): Expr = expr match {
case UnOp("-", UnOp("-", e)) =>
simplifyAll(e)   // `-' is its own inverse
case BinOp("+", e, Number(0)) =>
simplifyAll(e)   // `0' is a neutral element for `+'
case BinOp("*", e, Number(1)) =>
simplifyAll(e)   // `1' is a neutral element for `*'
case UnOp(op, e) =>
UnOp(op, simplifyAll(e))
case BinOp(op, l, r) =>
BinOp(op, simplifyAll(l), simplifyAll(r))
case _ => expr
}

scala> def simplifyBad(expr: Expr): Expr = expr match {
|   case UnOp(op, e) => UnOp(op, simplifyBad(e))
|   case UnOp("-", UnOp("-", e)) => e
| }
<console>:17: error: unreachable code
case UnOp("-", UnOp("-", e)) => e
^

```

### 15.5 Sealed classes

```
// In file pattern-matching/Misc.scala

sealed abstract class Expr
case class Var(name: String) extends Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr) extends Expr
case class BinOp(operator: String,
left: Expr, right: Expr) extends Expr

def describe(e: Expr): String = e match {
case Number(_) => "a number"
case Var(_)    => "a variable"
}

warning: match is not exhaustive!
missing combination           UnOp
missing combination          BinOp

// In file pattern-matching/Misc.scala

def describe(e: Expr): String = e match {
case Number(_) => "a number"
case Var(_) => "a variable"
case _ => throw new RuntimeException // Should not happen
}

// In file pattern-matching/Misc.scala

def describe(e: Expr): String = (e: @unchecked) match {
case Number(_) => "a number"
case Var(_)    => "a variable"
}

```

### 15.6 The Option type

```
scala> val capitals =
|   Map("France" -> "Paris", "Japan" -> "Tokyo")
capitals:
scala.collection.immutable.Map[java.lang.String,
java.lang.String] = Map(France -> Paris, Japan -> Tokyo)

scala> capitals get "France"
res21: Option[java.lang.String] = Some(Paris)

scala> capitals get "North Pole"
res22: Option[java.lang.String] = None

scala> def show(x: Option[String]) = x match {
|   case Some(s) => s
|   case None => "?"
| }
show: (Option[String])String

scala> show(capitals get "Japan")
res23: String = Tokyo

scala> show(capitals get "France")
res24: String = Paris

scala> show(capitals get "North Pole")
res25: String = ?

```

### 15.7 Patterns everywhere

```
scala> val myTuple = (123, "abc")
myTuple: (Int, java.lang.String) = (123,abc)

scala> val (number, string) = myTuple
number: Int = 123
string: java.lang.String = abc

scala> val exp = new BinOp("*", Number(5), Number(1))
exp: BinOp = BinOp(*,Number(5.0),Number(1.0))

scala> val BinOp(op, left, right) = exp
op: String = *
left: Expr = Number(5.0)
right: Expr = Number(1.0)

val withDefault: Option[Int] => Int = {
case Some(x) => x
case None => 0
}

scala> withDefault(Some(10))
res25: Int = 10

scala> withDefault(None)
res26: Int = 0

react {
case (name: String, actor: Actor) => {
actor ! getip(name)
act()
}
case msg => {
println("Unhandled message: "+ msg)
act()
}
}

val second: List[Int] => Int = {
case x :: y :: _ => y
}

<console>:17: warning: match is not exhaustive!
missing combination            Nil

scala> second(List(5,6,7))
res24: Int = 6

scala> second(List())
scala.MatchError: List()
at \$anonfun\$1.apply(<console>:17)
at \$anonfun\$1.apply(<console>:17)

val second: PartialFunction[List[Int],Int] = {
case x :: y :: _ => y
}

scala> second.isDefinedAt(List(5,6,7))
res27: Boolean = true

scala> second.isDefinedAt(List())
res28: Boolean = false

new PartialFunction[List[Int], Int] {
def apply(xs: List[Int]) = xs match {
case x :: y :: _ => y
}
def isDefinedAt(xs: List[Int]) = xs match {
case x :: y :: _ => true
case _ => false
}
}

scala> for ((country, city) <- capitals)
|   println("The capital of "+ country +" is "+ city)
The capital of France is Paris
The capital of Japan is Tokyo

scala> val results = List(Some("apple"), None,
|     Some("orange"))
results: List[Option[java.lang.String]] = List(Some(apple),
None, Some(orange))

scala> for (Some(fruit) <- results) println(fruit)
apple
orange

```

### 15.8 A larger example

```
BinOp("+",
BinOp("*",
BinOp("+", Var("x"), Var("y")),
Var("z")),
Number(1))

Map(
"|" -> 0, "||" -> 0,
"&" -> 1, "&&" -> 1, ...
)

// In file pattern-matching/ExprFormatter.scala

package org.stairwaybook.expr
import layout.Element.elem

sealed abstract class Expr
case class Var(name: String) extends Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr) extends Expr
case class BinOp(operator: String,
left: Expr, right: Expr) extends Expr

class ExprFormatter {

// Contains operators in groups of increasing precedence
private val opGroups =
Array(
Set("|", "||"),
Set("&", "&&"),
Set("^"),
Set("==", "!="),
Set("<", "<=", ">", ">="),
Set("+", "-"),
Set("*", "%")
)

// A mapping from operators to their precedence
private val precedence = {
val assocs =
for {
i <- 0 until opGroups.length
op <- opGroups(i)
} yield op -> i
Map() ++ assocs
}

private val unaryPrecedence = opGroups.length
private val fractionPrecedence = -1

// continued in Listing 15.21...

// In file pattern-matching/ExprFormatter.scala

// ...continued from Listing 15.20

private def format(e: Expr, enclPrec: Int): Element =

e match {

case Var(name) =>
elem(name)

case Number(num) =>
def stripDot(s: String) =
if (s endsWith ".0") s.substring(0, s.length - 2)
else s
elem(stripDot(num.toString))

case UnOp(op, arg) =>
elem(op) beside format(arg, unaryPrecedence)

case BinOp("/", left, right) =>
val top = format(left, fractionPrecedence)
val bot = format(right, fractionPrecedence)
val line = elem('-', top.width max bot.width, 1)
val frac = top above line above bot
if (enclPrec != fractionPrecedence) frac
else elem(" ") beside frac beside elem(" ")

case BinOp(op, left, right) =>
val opPrec = precedence(op)
val l = format(left, opPrec)
val r = format(right, opPrec + 1)
val oper = l beside elem(" "+ op +" ") beside r
if (enclPrec <= opPrec) oper
else elem("(") beside oper beside elem(")")
}

def format(e: Expr): Element = format(e, 0)
}

case Var(name) =>
elem(name)

case Number(num) =>
def stripDot(s: String) =
if (s endsWith ".0") s.substring(0, s.length - 2)
else s
elem(stripDot(num.toString))

case UnOp(op, arg) =>
elem(op) beside format(arg, unaryPrecedence)

case BinOp("/", left, right) =>
val top = format(left, fractionPrecedence)
val bot = format(right, fractionPrecedence)
val line = elem('-', top.width max bot.width, 1)
val frac = top above line above bot
if (enclPrec != fractionPrecedence) frac
else elem(" ") beside frac beside elem(" ")

case BinOp(op, left, right) =>
val opPrec = precedence(op)
val l = format(left, opPrec)
val r = format(right, opPrec + 1)
val oper = l beside elem(" "+ op +" ") beside r
if (enclPrec <= opPrec) oper
else elem("(") beside oper beside elem(")")

case BinOp("/", left, right) => ...

BinOp("-", Var("a"), BinOp("-", Var("b"), Var("c")))

// In file pattern-matching/Test.scala

import org.stairwaybook.expr._

object Express extends Application {

val f = new ExprFormatter

val e1 = BinOp("*", BinOp("/", Number(1), Number(2)),
BinOp("+", Var("x"), Number(1)))
val e2 = BinOp("+", BinOp("/", Var("x"), Number(2)),
BinOp("/", Number(1.5), Var("x")))
val e3 = BinOp("/", e1, e2)

def show(e: Expr) = println(f.format(e)+ "\n\n")

for (val e <- Array(e1, e2, e3)) show(e)
}

scala Express

```