Introducing Pattern Matching


       Ayush Kumar Mishra
      Sr. Software Consultant
              Knoldus
What is Pattern Matching
Pattern Matching lets you match a value against several
cases, sort of like a switch statement in Java. So in Java,
one might write something like this:

public boolean checkPrime(int number) {
  // checks if a number between 1 and 10 is prime
   switch (number) {
           case 1: return true;
           case 2: return true;
           case 3: return true;
           case 5: return true;
           case 7: return true;
           default: return false;
   }
}
●
    One of the major limitations of switch/case in Java
     (and really any C derivative language) is that it can
     only be used on primitives.
●
    You were unable to use strings as entries in switch-
     case statements.
●
    As the name suggests, pattern matching enables the
     checking of a sequence of tokens for the presence
     of the same pattern.
●
    The pattern matching is not something new. It is
     something that appears already in some other
     languages like OCaml, Haskell.
●
    A pattern match includes a sequence of alternatives
●
    An alternative starts with the keyword case.
      General syntax:
      case pattern => result
●
    Each alternative includes a pattern and one or more
     expressions, which will be evaluated if the pattern
     matches.
●
    An arrow symbol => separates the pattern from the
     expressions.
●
    Symbol _ is used to formulate the default case.
REPL

Traditional approach:
object Test {
    def knolX(x: Int): String = x match {
        case 1 => "one"
        case 2 => "two"
        case _ => "many"
    }
}
REPL

defined module Test


scala> Test.knolX(2)
res2: String = two


scala> Test.knolX(3)
res3: String = many


scala>
Constant Pattern
●
    ConstantMatch is a function that takes a parameter of
     type Any.
●
    Any is the Scala pendant to java.lang.Object.
●
    This value is now matched against several constants.
object Test {
    def knolX(x: Any): Any = x match {
         case 1 => "one"
         case "two" => 2
         case y: Int => "scala.Int"
         case _ => "many"
     }
}
REPL

defined module Test


scala> Test.knolX(1)
res4: Any = one


scala> Test.knolX("two")
res5: Any = 2


scala>
Matching Using case Classes:
●
    The case classes are special classes that are used
     in pattern matching with case expressions.

●
    Syntactically, these are standard classes with a
     special modifier: case.
●
    Case classes are classes with part of their
     behavior predefined in order to make easier their
     construction and their use in a pattern.
REPL
object Test {
val alice = new KnolX("Alice", 25)
val bob = new KnolX("Bob", 32)
val charlie = new KnolX("Charlie", 32)
         for (knolX <- List(alice, bob, charlie)) {
         knolX match {
             case KnolX("Alice", 25) => println("Hi Alice!")
             case KnolX("Bob", 32) => println("Hi Bob!")
         }
     }
     // case class, empty one.
    case class KnolX(name: String, age: Int)
}
scala> Test
Hi Alice!
Hi Bob!
Use OR in Case
def toYesOrNo(choice: Int): String = choice match {
     case 1 | 2 | 3 => "yes"
     case 0 => "no"
     case _ => "error"
 }
scala> toYesOrNo(1)
res7: String = yes
scala> toYesOrNo(2)
res8: String = yes
scala>
Functional approach to pattern matching
It provides an alternative way to design functions. For example, consider the
   factorial function. If you choose the recursive version, usually, you would
   define it like this:
def fact(n:Int):Int = if (n==0) 1 else n * factorial(n-1)
But you can use Scala’s pattern matching in this case:
def fact(n: Int): Int = n match {
     case 0 => 1
     case n => n * fact(n - 1)
 }
scala> fact(5)
res10: Int = 120
Pattern matching and collection

Pattern matching may be applied to the collections.
Below is a function that computes the length of a list without
  pattern matching:
def length[A](list : List[A]) : Int = {
     if (list.isEmpty) 0
     else 1 + length(list.tail)
 }
Here is the same function with pattern matching:In this function, there are
  two cases.
The last one checks if the list is empty with the Nil value. The first one
  checks if there is at least one element is the list.
The notation _ :: tail should be understood “a list with whatever head
  followed by a tail”. Here the tail can be Nil (ie. empty list) or a non-empty
  list.
def length[A](list : List[A]) : Int = list match {
     case _ :: tail => 1 + length(tail)
     case Nil => 0
 }
length: [A](list: List[A])Int


scala> length(List(1,2,3))


res8: Int = 3


scala> length(List())


res10: Int = 0
Typed pattern
        Type patterns allow to calculate the result based on the type of the value.
●   Use a type annotation to match certain types only:
    def whatIsIt(any: Any) = any match {
    case x: String => "A String: " + x
    case _: Int => "An Int value"
    case _ => "Something unknown"
    }
    scala> whatIsIt("12:01")
    res01: java.lang.String = A String: 12:01
    scala> whatIsIt(1)
    res1: java.lang.String = An Int value
    The typed pattern is always combined with the wildcard or variable pattern
Tuple pattern
• Use tuple syntax to match and decompose tuples:


    def whatIsIt(any: Any) = any match {
    case ("12:00", "12:01") => "12:00..12:01"
    case ("12:00", x) => "High noon and " + x
    case _ => "Something else"
    }
•
    scala> whatIsIt("12:00" -> "midnight")
    res0: java.lang.String = High noon and midnight


• The tuple pattern is combined with other patterns, e.g. with the
   constant or variable pattern
Constructor pattern

●   Use constructor syntax to match and decompose case classes:
    def whatIsIt(any: Any) = any match {
    case Time(12, 00) => "High noon"
    case Time(12, minutes) => "12:%02d" format minutes
    }


    scala> whatIsIt(Time(12, 01))
    res0: java.lang.String = 12:01
●   The constructor pattern is combined with other pattern, e.g. with the
     constant or variable pattern or with deeply nested constructor
     patterns
For-expressions
   with Scala
●
    For-expressions are for iteration, but they aren’t loops,
     they yield a collection.
●
    General syntax:
             for (seq) yield expr
●
    seq contains generators, definitions and filters
●
    expr creates an element of the resulting collection
Generators

●
    Generators drive the iteration
         x <- coll
●
    coll is the collection to be iterated .
●
    x is a variable bound to the current element of the iteration.
●
    The (first) generator determines the type of the result:

●
    scala> for (i <- List(1, 2, 3)) yield i + 1
    res0: List[Int] = List(2, 3, 4)
●
    scala> for (i <- Set(1, 2, 3)) yield i + 1
    res1: ...Set[Int] = Set(2, 3, 4)
Multiple generators

●
    Either separate multiple generators by semicolon
●
    Or better use curly braces and new lines:
●
    scala> for {
    | i <- 1 to 3
    | j <- 1 to i
    | } yield i * j
    res0: ...IndexedSeq[Int] = Vector(1, 2, 4, 3, 6, 9)
Filters

●
    Filters control the iteration
    if expr
●
    expr must evaluate to a Boolean
●
    Filters can follow generators without semicolon or new line:
●
    scala> for {
    | i <- 1 to 3 if i % 2 == 1
    | j <- 1 to i
    | } yield i * j
    res0: ...IndexedSeq[Int] = Vector(1, 3, 6, 9)
Definitions
●
    Definitions are like local val definitions
    x = expr
●
    Definitions can also be directly followed by a filter:
●
    scala> for {
    | time <- times
    | hours = time.hours if hours > 12
    | } yield (hours - 12) + "pm"
    res0: List[String] = List(1pm, 2pm)

Introducing Pattern Matching in Scala

  • 1.
    Introducing Pattern Matching Ayush Kumar Mishra Sr. Software Consultant Knoldus
  • 2.
    What is PatternMatching Pattern Matching lets you match a value against several cases, sort of like a switch statement in Java. So in Java, one might write something like this: public boolean checkPrime(int number) { // checks if a number between 1 and 10 is prime switch (number) { case 1: return true; case 2: return true; case 3: return true; case 5: return true; case 7: return true; default: return false; } }
  • 3.
    One of the major limitations of switch/case in Java (and really any C derivative language) is that it can only be used on primitives. ● You were unable to use strings as entries in switch- case statements. ● As the name suggests, pattern matching enables the checking of a sequence of tokens for the presence of the same pattern. ● The pattern matching is not something new. It is something that appears already in some other languages like OCaml, Haskell.
  • 4.
    A pattern match includes a sequence of alternatives ● An alternative starts with the keyword case. General syntax: case pattern => result ● Each alternative includes a pattern and one or more expressions, which will be evaluated if the pattern matches. ● An arrow symbol => separates the pattern from the expressions. ● Symbol _ is used to formulate the default case.
  • 5.
    REPL Traditional approach: object Test{ def knolX(x: Int): String = x match { case 1 => "one" case 2 => "two" case _ => "many" } }
  • 6.
    REPL defined module Test scala>Test.knolX(2) res2: String = two scala> Test.knolX(3) res3: String = many scala>
  • 7.
    Constant Pattern ● ConstantMatch is a function that takes a parameter of type Any. ● Any is the Scala pendant to java.lang.Object. ● This value is now matched against several constants. object Test { def knolX(x: Any): Any = x match { case 1 => "one" case "two" => 2 case y: Int => "scala.Int" case _ => "many" } }
  • 8.
    REPL defined module Test scala>Test.knolX(1) res4: Any = one scala> Test.knolX("two") res5: Any = 2 scala>
  • 9.
    Matching Using caseClasses: ● The case classes are special classes that are used in pattern matching with case expressions. ● Syntactically, these are standard classes with a special modifier: case. ● Case classes are classes with part of their behavior predefined in order to make easier their construction and their use in a pattern.
  • 10.
    REPL object Test { valalice = new KnolX("Alice", 25) val bob = new KnolX("Bob", 32) val charlie = new KnolX("Charlie", 32) for (knolX <- List(alice, bob, charlie)) { knolX match { case KnolX("Alice", 25) => println("Hi Alice!") case KnolX("Bob", 32) => println("Hi Bob!") } } // case class, empty one. case class KnolX(name: String, age: Int) } scala> Test Hi Alice! Hi Bob!
  • 11.
    Use OR inCase def toYesOrNo(choice: Int): String = choice match { case 1 | 2 | 3 => "yes" case 0 => "no" case _ => "error" } scala> toYesOrNo(1) res7: String = yes scala> toYesOrNo(2) res8: String = yes scala>
  • 12.
    Functional approach topattern matching It provides an alternative way to design functions. For example, consider the factorial function. If you choose the recursive version, usually, you would define it like this: def fact(n:Int):Int = if (n==0) 1 else n * factorial(n-1) But you can use Scala’s pattern matching in this case: def fact(n: Int): Int = n match { case 0 => 1 case n => n * fact(n - 1) } scala> fact(5) res10: Int = 120
  • 13.
    Pattern matching andcollection Pattern matching may be applied to the collections. Below is a function that computes the length of a list without pattern matching: def length[A](list : List[A]) : Int = { if (list.isEmpty) 0 else 1 + length(list.tail) }
  • 14.
    Here is thesame function with pattern matching:In this function, there are two cases. The last one checks if the list is empty with the Nil value. The first one checks if there is at least one element is the list. The notation _ :: tail should be understood “a list with whatever head followed by a tail”. Here the tail can be Nil (ie. empty list) or a non-empty list. def length[A](list : List[A]) : Int = list match { case _ :: tail => 1 + length(tail) case Nil => 0 }
  • 15.
    length: [A](list: List[A])Int scala>length(List(1,2,3)) res8: Int = 3 scala> length(List()) res10: Int = 0
  • 16.
    Typed pattern Type patterns allow to calculate the result based on the type of the value. ● Use a type annotation to match certain types only: def whatIsIt(any: Any) = any match { case x: String => "A String: " + x case _: Int => "An Int value" case _ => "Something unknown" } scala> whatIsIt("12:01") res01: java.lang.String = A String: 12:01 scala> whatIsIt(1) res1: java.lang.String = An Int value The typed pattern is always combined with the wildcard or variable pattern
  • 17.
    Tuple pattern • Usetuple syntax to match and decompose tuples: def whatIsIt(any: Any) = any match { case ("12:00", "12:01") => "12:00..12:01" case ("12:00", x) => "High noon and " + x case _ => "Something else" } • scala> whatIsIt("12:00" -> "midnight") res0: java.lang.String = High noon and midnight • The tuple pattern is combined with other patterns, e.g. with the constant or variable pattern
  • 18.
    Constructor pattern ● Use constructor syntax to match and decompose case classes: def whatIsIt(any: Any) = any match { case Time(12, 00) => "High noon" case Time(12, minutes) => "12:%02d" format minutes } scala> whatIsIt(Time(12, 01)) res0: java.lang.String = 12:01 ● The constructor pattern is combined with other pattern, e.g. with the constant or variable pattern or with deeply nested constructor patterns
  • 19.
    For-expressions with Scala
  • 20.
    For-expressions are for iteration, but they aren’t loops, they yield a collection. ● General syntax: for (seq) yield expr ● seq contains generators, definitions and filters ● expr creates an element of the resulting collection
  • 21.
    Generators ● Generators drive the iteration x <- coll ● coll is the collection to be iterated . ● x is a variable bound to the current element of the iteration. ● The (first) generator determines the type of the result: ● scala> for (i <- List(1, 2, 3)) yield i + 1 res0: List[Int] = List(2, 3, 4) ● scala> for (i <- Set(1, 2, 3)) yield i + 1 res1: ...Set[Int] = Set(2, 3, 4)
  • 22.
    Multiple generators ● Either separate multiple generators by semicolon ● Or better use curly braces and new lines: ● scala> for { | i <- 1 to 3 | j <- 1 to i | } yield i * j res0: ...IndexedSeq[Int] = Vector(1, 2, 4, 3, 6, 9)
  • 23.
    Filters ● Filters control the iteration if expr ● expr must evaluate to a Boolean ● Filters can follow generators without semicolon or new line: ● scala> for { | i <- 1 to 3 if i % 2 == 1 | j <- 1 to i | } yield i * j res0: ...IndexedSeq[Int] = Vector(1, 3, 6, 9)
  • 24.
    Definitions ● Definitions are like local val definitions x = expr ● Definitions can also be directly followed by a filter: ● scala> for { | time <- times | hours = time.hours if hours > 12 | } yield (hours - 12) + "pm" res0: List[String] = List(1pm, 2pm)