- Concepts of Programming Languages

Pattern Matching

Instructor:

Learning Objectives

How to decompose and process complex nested data structures?

  • Identify matched expression and cases in Scala pattern matching
  • Express functions with pattern matching

Pattern Matching on Tuples

Pattern matching branches and binds pattern variables

Sum of the components of a tuple

  • Decomposition with index access
    1. def sum (p: (Int,Int)) : Int =
    2. if p==null then throw MatchError(p)
    3. val x = p(0)
    4. val y = p(1)
    5. x + y
    6. end sum
  • Pattern matching

    1. def sum (p: (Int,Int)) = p match
    2. case (x,y) => x+y
  • With types (optional)

    1. def sum (p: (Int,Int)) : Int = p match
    2. case (x: Int, y: Int) => x+y
  • Every case args => body is a function

Pattern Matching on Algebraic Datatype

Sum of the first two elements of a linked list

  1. enum LinkedList:
  2. case Empty
  3. case Cons(head: Int, tail: LinkedList)
  • Decomposition with field access
    1. def sum (p: LinkedList) : Int =
    2. if p==null || p==Empty then
    3. throw IllegalArgumentException("...")
    4. val l = p.asInstanceOf[Cons]
    5. if l.tail==null || l.tail==Empty then
    6. throw IllegalArgumentException("...")
    7. val x = l.head
    8. val y = l.tail.asInstanceOf[Cons].head
    9. x + y
    10. end sum
  • Pattern matching
    1. def sum (p: LinkedList) = p match
    2. case Cons(x, Cons(y, tail)) => x+y
    3. case _ => throw IllegalArgumentException("...")
  • Every case args => body is a function

Pattern Matching on Lists

Sum of the first two elements of a builtin list

  • Decomposition with projections
    1. def sum (p: List[Int]) : Int =
    2. if p==null || p==Empty then
    3. throw IllegalArgumentException("...")
    4. if l.tail==null || l.tail==Empty then
    5. throw IllegalArgumentException("...")
    6. val x = l.head
    7. val y = l.tail.head
    8. val rest = l.tail.tail
    9. x + y
    10. end sum
  • Decomposition with pattern matching
    1. def sum(xs: List[Int]) : Int = xs match
    2. case x :: y :: tail => x+y
    3. case _ => throw IllegalArgumentException("...")
    4. end sum

Pattern Matching on Lists

  • Omit unnecessary variables and types
  • Decomposition with projections
    1. def sum (p: List[Int]) : Int =
    2. if p==null || p==Empty then
    3. throw IllegalArgumentException("...")
    4. if l.tail==null || l.tail==Empty then
    5. throw IllegalArgumentException("...")
    6. val x = l.head
    7. val y = l.tail.head
    8. // val rest = l.tail.tail
    9. x + y
    10. end sum
  • Decomposition with pattern matching

    1. def sum(xs: List[Int]) : Int = xs match
    2. case x :: y :: _ => x+y
    3. case _ => throw IllegalArgumentException("...")
    4. end sum
  • Wildcard operator _ means don't care

Nested Patterns

  • Nested patterns: patterns can include other patterns
  • Print first tuple of a singleton list, else print second int
  1. def f (xs: List[(Int,String)]) = xs match
  2. case Nil => "List is empty"
  3. case x :: Nil => s"List has one element: $x"
  4. case _ :: (x,_) :: _ => s"The second int is $x"
  5. end f
  6. val zs = List ((1,"dog"), (2,"cat"), (3,"sloth"))
  7. f(zs) // 2

Exercise: Linked List

  1. enum LinkedList:
  2. case Empty
  3. case Cons (head: Int, tail: LinkedList)
  4. end LinkedList

Create an empty list?

  • Simply use Empty

    1. import LinkedList.*
    2. val xs = Empty
  • Create an instance of a list?

  • Nest Cons and terminate with Empty

    1. import LinkedList.*
    2. val xs = Cons (1, Cons(2, Cons(3, Empty)))

Exercise: Linked List ADT

  1. enum LinkedList:
  2. case Empty
  3. case Cons (head: Int, tail: LinkedList)
  4. end LinkedList
  5. object LinkedList:
  6. def apply(elems: Int*) : LinkedList =
  7. if elems.isEmpty then Empty
  8. else Cons(elems.head, LinkedList(elems.tail*))
  9. end apply
  10. end LinkedList
  • Create an instance of a list?

  • Use method apply (name can be omitted)

    1. import LinkedList.*
    2. // val xs = Cons (1, Cons(2, Cons(3, Empty)))
    3. val xs = LinkedList(1, 2, 3) // LinkedList.apply(1, 2, 3)

Exercise: Linked List ADT

  • Implement simple list operations by pattern matching

isEmpty

  1. def isEmpty (xs: List[Int]) =
  2. xs match
  3. case Nil => true
  4. case _ => false
  5. end isEmpty

head

  1. def head (xs: List[Int]) =
  2. xs match
  3. case Nil =>
  4. throw NoSuchElementException()
  5. case y :: _ => y
  6. end head

tail

  1. def tail (xs:List[Int]) =
  2. xs match
  3. case Nil =>
  4. throw NoSuchElementException()
  5. case _ :: ys => ys
  6. end tail
  • Many list operations are builtin:
    • List (1, 2, 3).head
    • List (1, 2, 3).tail
    • List (1, 2, 3).isEmpty

Summary

  • Pattern matching to decompose lists, tuples, and objects into their components
  • Pattern matching branches and binds variables
  • Every case args => body is a function
  • First matching function is evaluated

# <span class="fa-stack"><i class="fa-solid fa-circle fa-stack-2x"></i><i class="fa-solid fa-dumbbell fa-stack-1x fa-inverse"></i></span> Pattern Matching Example: Lists - :fa fa-terminal: ```text In Scala, implement a method that takes a list of at least 3 numbers and returns it with the first 3 numbers sorted in ascending order ``` * :fa fa-comment-dots: <div class="text-base"> ```scala def f (numbers: List[Int]) : List[Int] = { require(numbers.length >= 3, "The list must contain at least 3 elements") val (firstThree, rest) = numbers.splitAt(3) firstThree.sorted ++ rest } ensuring { (result: List[Int]) => ??? } ``` </div> - Contract for checking the first 3 elements <div class="grid grid-cols-3 gap-4"> <div data-marpit-fragment> **Index access** ```scala ensuring { (result: List[Int]) => val x1 = result(0) val x2 = result(1) val x3 = result(2) x1 <= x2 && x2 <= x3 } ``` </div> <div data-marpit-fragment> **Projections** ```scala ensuring { (result: List[Int]) => val x1 = result.head val x2 = result.tail.head val x3 = result.tail.tail.head x1 <= x2 && x2 <= x3 } ``` </div> <div data-marpit-fragment> **Pattern matching** ```scala ensuring { (result: List[Int]) => val x1 :: x2 :: x3 :: _ = result x1 <= x2 && x2 <= x3 } ``` ```scala ensuring { case x1 :: x2 :: x3 :: _ => x1 <= x2 && x2 <= x3 } ``` </div> </div> ---

* With types (optional) ```scala def sum (p: LinkedList) : Int = p match case Cons(x: Int, Cons(y: Int, tail: LinkedList)) => x+y case _ => throw IllegalArgumentException("...") ```

# <span class="fa-stack"><i class="fa-solid fa-circle fa-stack-2x"></i><i class="fa-solid fa-cubes fa-stack-1x fa-inverse"></i></span> Pattern Matching - Pattern matching vs. Projections <div class="grid grid-cols-2 gap-4"> <div> - Decomposition with pattern matching ```scala def f (xs: List[(Int,String)]) = xs match case Nil => "List is empty" case x :: Nil => s"List has one element: $x" case _ :: (x,_) :: _ => s"The second int is $x" end f val zs = List ((11,"dog"), (21,"cat"), (31,"sloth")) f(zs) // 21 ``` </div> <div> * Decomposition with projections ```scala def f (xs: List[(Int,String)]) = if xs == Nil then "List is empty" else if xs.tail == Nil then s"List has one element: ${xs.head}" else s"The second int is ${xs.tail.head(0)}" end f val zs = List ((11,"dog"), (21,"cat"), (31,"sloth")) f(zs) // 21 ``` </div> </div> ---

# <span class="fa-stack"><i class="fa-solid fa-circle fa-stack-2x"></i><i class="fa-solid fa-dumbbell fa-stack-1x fa-inverse"></i></span> Exercise: Linked List ADT - Open project `hw1` in VSCode, create a file `week2-patterns.sc` in folder `play` <div class="grid grid-cols-2 gap-4"> <div class="groupblock"> **Github Copilot** - Deactivate automatic suggestions - Add keyboard shortcut: `Cmd-Shift-P` - trigger inline suggestions - Chat commands <div class="text-sm"> - `/assess-exercise` - `/explain-code` - `/explain-compile-error` - `/find-resources` - `/infer-types` - `/make-exercise-plan` - `/step-exec` </div> </div> <div class="groupblock"> **Scala REPL** - Open a terminal - Open the Scala REPL: `sbt console` - Load and execute files `:load play/week2-patterns.sc` </div> </div> ---

# <span class="fa-stack"><i class="fa-solid fa-circle fa-stack-2x"></i><i class="fa-solid fa-dumbbell fa-stack-1x fa-inverse"></i></span> Exercise: Linked List ADT :fa fa-question-circle: Which "states" does a list have :fa fa-arrow-right: which case classes and case objects? - An `Empty` list and a `Cons` cell of at least one element ```scala enum LinkedList: case Empty case Cons (head: Int, tail: LinkedList) end LinkedList ``` ---