Code Examples for

Programming in Scala, Second Edition

Return to chapter index

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
scala> op.copy(operator = "-") res4: BinOp = BinOp(-,Number(1.0),Var(x))
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) res6: java.lang.String = five scala> describe(true) res7: java.lang.String = truth scala> describe("hello") res8: java.lang.String = hi! scala> describe(Nil) res9: java.lang.String = the empty list scala> describe(List(1,2,3)) res10: 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" | } res11: java.lang.String = OK
scala> val pi = math.Pi pi: Double = 3.141592653589793 scala> E match { | case pi => "strange math? Pi = "+ pi | } res12: java.lang.String = strange math? Pi = 2.718281828459045
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" | } res14: 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") res16: Int = 3 scala> generalSize(Map(1 -> 'a', 2 -> 'b')) res17: Int = 2 scala> generalSize(math.Pi) res18: 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: (x: Any)Boolean
scala> :quit $ scala -unchecked Welcome to Scala version 2.8.1 (Java HotSpot(TM) Client VM, Java 1.5.0_13). Type in expressions to have them evaluated. Type :help for more information. 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)) res19: Boolean = true scala> isIntIntMap(Map("abc" -> "abc")) res20: Boolean = true
scala> def isStringArray(x: Any) = x match { | case a: Array[String] => "yes" | case _ => "no" | } isStringArray: (x: Any)java.lang.String scala> val as = Array("abc") as: Array[java.lang.String] = Array(abc) scala> isStringArray(as) res21: java.lang.String = yes scala> val ai = Array(1, 2, 3) ai: Array[Int] = Array(1, 2, 3) scala> isStringArray(ai) res22: 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>:11: 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 | } simplifyAdd: (e: Expr)Expr
// 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>:18: 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
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" res23: Option[java.lang.String] = Some(Paris) scala> capitals get "North Pole" res24: Option[java.lang.String] = None
scala> def show(x: Option[String]) = x match { | case Some(s) => s | case None => "?" | } show: (x: Option[String])String scala> show(capitals get "Japan") res25: String = Tokyo scala> show(capitals get "France") res26: String = Paris scala> show(capitals get "North Pole") res27: 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)) res28: Int = 10 scala> withDefault(None) res29: 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)) res30: Boolean = true scala> second.isDefinedAt(List()) res31: 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 org.stairwaybook.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 assocs.toMap } 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 (e <- Array(e1, e2, e3)) show(e) }
scala Express

15.9 Conclusion

For more information about Programming in Scala, Second Edition (the "Stairway Book"), please visit:

http://www.artima.com/shop/programming_in_scala_2ed

and:

http://booksites.artima.com/programming_in_scala_2ed

Copyright © 2007-2010 Artima, Inc. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.