Code Examples for

Programming in Scala, Fourth 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 case-classes-and-pattern-matching/Misc.scala expr match { case BinOp(op, left, right) => println(s"$expr is a binary operation") case _ => }

15.2 Kinds of patterns


// In file case-classes-and-pattern-matching/Misc.scala expr match { case BinOp(op, left, right) => println(s"$expr is a binary operation") case _ => // handle the default case }
// In file case-classes-and-pattern-matching/Misc.scala expr match { case BinOp(_, _, _) => println(s"$expr is a binary operation") case _ => println("It's something else") }
// In file case-classes-and-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: String = five scala> describe(true) res7: String = truth scala> describe("hello") res8: String = hi! scala> describe(Nil) res9: String = the empty list scala> describe(List(1,2,3)) res10: String = something else
// In file case-classes-and-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: String = OK
scala> val pi = math.Pi pi: Double = 3.141592653589793 scala> E match { | case pi => "strange math? Pi = " + pi | } res12: String = strange math? Pi = 2.718281828459045
scala> E match { | case pi => "strange math? Pi = " + pi | case _ => "OK" | } case pi => "strange math? Pi = " + pi ^ On line 2: warning: patterns after a variable pattern cannot match (SLS 8.1.1)
scala> E match { | case `pi` => "strange math? Pi = " + pi | case _ => "OK" | } res14: String = OK
// In file case-classes-and-pattern-matching/Misc.scala expr match { case BinOp("+", e, Number(0)) => println("a deep match") case _ => }
// In file case-classes-and-pattern-matching/Misc.scala expr match { case List(0, _, _) => println("found it") case _ => }
// In file case-classes-and-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 case-classes-and-pattern-matching/Misc.scala expr.isInstanceOf[String]
// In file case-classes-and-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 | } case m: Map[Int, Int] => true ^ On line 2: warning: non-variable type argument Int in type pattern scala.collection.immutable.Map[Int,Int] (the underlying of Map[Int,Int]) is unchecked since it is eliminated by erasure
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)String scala> val as = Array("abc") as: Array[String] = Array(abc) scala> isStringArray(as) res21: String = yes scala> val ai = Array(1, 2, 3) ai: Array[Int] = Array(1, 2, 3) scala> isStringArray(ai) res22: String = no
// In file case-classes-and-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 | } case BinOp("+", x, x) => BinOp("*", x, Number(2)) ^ On line 2: error: x is already defined as value x
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 | } case UnOp("-", UnOp("-", e)) => e ^ On line 3: warning: unreachable code simplifyBad: (expr: Expr)Expr

15.5 Sealed classes


// In file case-classes-and-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 case-classes-and-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[String,String] = Map(France -> Paris, Japan -> Tokyo) scala> capitals get "France" res23: Option[String] = Some(Paris) scala> capitals get "North Pole" res24: Option[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, String) = (123,abc) scala> val (number, string) = myTuple number: Int = 123 string: 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
var sum = 0 def receive = { case Data(byte) => sum += byte case GetChecksum(requester) => val checksum = ~(sum & 0xFF) + 1 requester ! checksum }
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[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 case-classes-and-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 case-classes-and-pattern-matching/ExprFormatter.scala // ...continued from Listing 15.20 import org.stairwaybook.layout.Element 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 case-classes-and-pattern-matching/Test.scala import org.stairwaybook.expr._ object Express extends App { 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) = s"${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, Fourth Edition (the "Stairway Book"), please visit:

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

and:

http://booksites.artima.com/programming_in_scala_4ed

Copyright © 2007-2019 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.