Code Examples for

Programming in Scala, Fifth Edition

Return to chapter index

13 Pattern Matching

Sample run of chapter's interpreter examples

13.1 A simple example


trait Expr case class Var(name: String) extends Expr case class Num(number: Double) extends Expr case class UnOp(operator: String, arg: Expr) extends Expr case class BinOp(operator: String, left: Expr, right: Expr) extends Expr
val v = Var("x")
val op = BinOp("+", Num(1), v)
v.name // x op.left // Num(1.0)
op.toString // BinOp(+,Num(1.0),Var(x)) op.right == Var("x") // true
op.copy(operator = "-") // BinOp(-,Num(1.0),Var(x))
UnOp("-", UnOp("-", e)) => e // Double negation BinOp("+", e, Num(0)) => e // Adding zero BinOp("*", e, Num(1)) => e // Multiplying by one
simplifyTop(UnOp("-", UnOp("-", Var("x")))) // Var(x)
def simplifyTop(expr: Expr): Expr = expr match case UnOp("-", UnOp("-", e)) => e // Double negation case BinOp("+", e, Num(0)) => e // Adding zero case BinOp("*", e, Num(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(s"$expr is a binary operation") case _ =>

13.2 Kinds of patterns


// In file pattern-matching/Misc.scala expr match case BinOp(op, left, right) => s"$expr is a binary operation" case _ => // handle the default case s"It's something else"
// In file pattern-matching/Misc.scala expr match case BinOp(_, _, _) => s"$expr is a binary operation" case _ => "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"
describe(5) // five describe(true) // truth describe("hello") // hi! describe(Nil) // the empty list describe(List(1,2,3)) // something else
// In file pattern-matching/Misc.scala expr match case 0 => "zero" case somethingElse => s"not zero $somethingElse"
scala> import math.{E, Pi} import math.{E, Pi} scala> E match | case Pi => s"strange math? Pi = $Pi" | case _ => "OK" | val res0: String = OK
scala> val pi = math.Pi pi: Double = 3.141592653589793 scala> E match | case pi => s"strange math? Pi = $pi" | val res1: String = strange math? Pi = 2.718281828459045
scala> E match | case pi => s"strange math? Pi = $pi" | case _ => "OK" | val res2: String = strange math? Pi = 2.718281828459045 3 | case _ => "OK" | ^ | Unreachable case
scala> E match | case `pi` => s"strange math? Pi = $pi" | case _ => "OK" | res4: String = OK
// In file pattern-matching/Misc.scala expr match case BinOp("+", e, Num(0)) => "a deep match" case _ => ""
// In file pattern-matching/Misc.scala xs match case List(0, _, _) => "found it" case _ => ""
// In file pattern-matching/Misc.scala xs match case List(0, _*) => "found it" case _ => ""
def tupleDemo(obj: Any) = obj match case (a, b, c) => s"matched ab$c" case _ => ""
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
generalSize("abc") // 3 generalSize(Map(1 -> 'a', 2 -> 'b')) // 2 generalSize(math.Pi) // -1
// In file pattern-matching/Misc.scala expr.isInstanceOf[String]
// In file pattern-matching/Misc.scala expr.asInstanceOf[String]
if x.isInstanceOf[String] then val s = x.asInstanceOf[String] s.length else ...
3.asInstanceOf[String] // java.lang.ClassCastException: java.lang.Integer // cannot be cast to java.lang.String
scala> 3: String // ': String' is the type ascription 1 |3: String |^ |Found: (3 : Int) |Required: String
scala> Var("x"): Expr // Expr is a supertype of Var val res0: Expr = Var(x)
scala> 3: Long val res1: Long = 3
scala> def isIntIntMap(x: Any) = | x match | case m: Map[Int, Int] => true | case _ => false | def isIntIntMap(x: Any): Boolean 3 | case m: Map[Int, Int] => true | ^^^^^^^^^^^^^^^^ | the type test for Map[Int, Int] cannot be | checked at runtime
isIntIntMap(Map(1 -> 1)) // true isIntIntMap(Map("abc" -> "abc")) // true
def isStringArray(x: Any) = x match case a: Array[String] => "yes" case _ => "no" isStringArray(Array("abc")) // yes isStringArray(Array(1, 2, 3)) // no
// In file pattern-matching/Misc.scala expr match case UnOp("abs", e @ UnOp("abs", _)) => e case _ =>

13.3 Pattern guards


BinOp("+", Var("x"), Var("x"))
BinOp("*", Var("x"), Num(2))
scala> def simplifyAdd(e: Expr) = | e match | case BinOp("+", x, x) => BinOp("*", x, Num(2)) | case _ => e | 3 | case BinOp("+", x, x) => BinOp("*", x, Num(2)) | ^ | duplicate pattern variable: x
def simplifyAdd(e: Expr) = e match case BinOp("+", x, y) if x == y => BinOp("*", x, Num(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' => ...

13.4 Pattern overlaps


def simplifyAll(expr: Expr): Expr = expr match case UnOp("-", UnOp("-", e)) => simplifyAll(e) // `-' is its own inverse case BinOp("+", e, Num(0)) => simplifyAll(e) // `0' is a neutral element for `+' case BinOp("*", e, Num(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 _ => expr | def simplifyBad(expr: Expr): Expr 4 | case UnOp("-", UnOp("-", e)) => e | ^^^^^^^^^^^^^^^^^^^^^^^ | Unreachable case

13.5 Sealed classes


// In file pattern-matching/Misc.scala sealed trait Expr case class Var(name: String) extends Expr case class Num(number: 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 Num(_) => "a number" case Var(_) => "a variable"
def describe(e: Expr): String 2 | e match | ^ | match may not be exhaustive. | | It would fail on pattern case: UnOp(_, _), | BinOp(_, _, _)
def describe(e: Expr): String = e match case Num(_) => "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 Num(_) => "a number" case Var(_) => "a variable"

13.6 Pattern matching Options


val capitals = Map("France" -> "Paris", "Japan" -> "Tokyo") capitals.get("France") // Some(Paris) capitals.get("North Pole") // None
def show(x: Option[String]) = x match case Some(s) => s case None => "?" show(capitals.get("Japan")) // Tokyo show(capitals.get("France")) // Paris show(capitals.get("North Pole")) // ?

13.7 Patterns everywhere


scala> val myTuple = (123, "abc") val myTuple: (Int, String) = (123,abc) scala> val (number, string) = myTuple val number: Int = 123 val string: String = abc
scala> val exp = new BinOp("*", Num(5), Num(1)) val exp: BinOp = BinOp(*,Num(5.0),Num(1.0)) scala> val BinOp(op, left, right) = exp val op: String = * val left: Expr = Num(5.0) val right: Expr = Num(1.0)
val withDefault: Option[Int] => Int = case Some(x) => x case None => 0
withDefault(Some(10)) // 10 withDefault(None) // 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
2 | case x :: y :: _ => y | ^ | match may not be exhaustive. | | It would fail on pattern case: List(_), Nil
scala> second(List(5, 6, 7)) val res24: Int = 6 scala> second(List()) scala.MatchError: List() (of class Nil$) at rs$line$10$.$init$$$anonfun$1(rs$line$10:2) at rs$line$12$.<init>(rs$line$12:1)
val second: PartialFunction[List[Int],Int] = case x :: y :: _ => y
second.isDefinedAt(List(5,6,7)) // true second.isDefinedAt(List()) // 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
for (country, city) <- capitals yield s"The capital of country is city" // // List(The capital of France is Paris, // The capital of Japan is Tokyo)
val results = List(Some("apple"), None, Some("orange")) for Some(fruit) <- results yield fruit // List(apple, orange)

13.8 A larger example


BinOp("+", BinOp("*", BinOp("+", Var("x"), Var("y")), Var("z")), Num(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 Num(number: 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 = Vector( 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 13.21...
// In file pattern-matching/ExprFormatter.scala // ...continued from Listing 13.20 import org.stairwaybook.layout.Element private def format(e: Expr, enclPrec: Int): Element = e match case Var(name) => elem(name) case Num(number) => def stripDot(s: String) = if s endsWith ".0" then s.substring(0, s.length - 2) else s elem(stripDot(number.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 then 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 then oper else elem("(") beside oper beside elem(")") end match def format(e: Expr): Element = format(e, 0) end ExprFormatter
case Var(name) => elem(name)
case Num(number) => def stripDot(s: String) = if s endsWith ".0" then s.substring(0, s.length - 2) else s elem(stripDot(number.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 then 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 then 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: def main(args: Array[String]): Unit = val f = new ExprFormatter val e1 = BinOp("*", BinOp("/", Num(1), Num(2)), BinOp("+", Var("x"), Num(1))) val e2 = BinOp("+", BinOp("/", Var("x"), Num(2)), BinOp("/", Num(1.5), Var("x"))) val e3 = BinOp("/", e1, e2) def show(e: Expr) = println(s"${f.format(e)}\n\n") for e <- Vector(e1, e2, e3) do show(e)
scala Express

13.9 Conclusion

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

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

and:

http://booksites.artima.com/programming_in_scala_5ed

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