Scala Foundation Course - Pattern Matching


Pattern matching is not new. It existed for decades as one of the most powerful functional programming technique. But it hasn't been seen before in mainstream languages. Scala takes the credit to bring pattern matching to the centre of the programming style. So, the next question is this.

What is Scala pattern matching?

When we talk about pattern matching, the first thing that comes to our mind is a string matching or a regular expression. But in the context of functional programming, this terminology takes a new meaning. Instead of regular expression matching, the Functional Programming is going to look into matching objects against other objects.
What does it mean?
The most fundamental example of object comparison is a type checking. Assume you got an object, and you want to test it for following possibilities.

  1. Does it match with a String Object?
  2. Does it match with an Integer Object?
  3. Does it match with a Double Object?
  4. Or Is it something else?

Here is the code for such a comparison.

                                
    def myTest(x:Any) = {
        x match {
            case i:Integer =>  "It's an Integer = " + i
            case s:String  =>  "It's an String = " + s
            case d:Double  =>  "It's a double = " + d
            case _         =>  "Opps! Somthing Else"
        }
    }
    //Let me test my code.
    println(myTest("abc"))
    println(myTest(2.5))
    println(myTest(2)) 
    println(myTest(1 to 10))                                    
                         

This example is the most basic kind of pattern matching. We call it a typed pattern match. We use this kind of pattern matching as a convenient replacement for type tests and type casts. If you want to do the same thing in Java, you would be using a bunch of instancOf tests. And when the test succeeds, you need to do a cast. But Scala pattern matching gives you a convenient alternative which looks like Java's switch statement. But instead of matching numbers, that's what a switch does. You are matching object types. The Scala's pattern matching also binds the value on a pattern match. In this example, when the object x matches with an Integer type, Scala binds it to i. If it matches with a String type, Scala ties it to s. This automatic binding saves you from casting the x to a matching type.
The type test is a common problem. Let's say you are dealing with a JSON or an XML object. JSON or XML is a tree of various data objects, and those individual objects may be a customer name, a phone number or address. You don't know the type of each element, and hence you have a problem. The only way to deal with them is to have a bunch of InstancOf tests. In every case, you are asking a question. Is this a Phone number? Is this a customer name? If the answer is yes, you need to cast it to a phone number object. This approach is rather ugly and clumsy. Pattern matching does the same thing in a much safer and more natural way.
The typed pattern match is just one. There are many other kinds of pattern matching. I am not going to cover all of them here. But before we close this part, it is important to understand the purpose of pattern matching.

Why do we need pattern matching?


If we take an object-oriented approach, we encapsulate everything into an object, the data structure as well as the methods to work on those data structures. Both are encapsulated to create an object. And then, we don't look at the data structures within the object. We just use methods. Right?
When we need new functionality or a feature, we add new methods to the object definition. Correct? This approach works perfectly fine if you have a fixed set of operations that you want to perform on the data structure. Even if you occasionally want to add new methods to the object, this approach is perfectly fine.
The problem comes when you want to perform new operations on those data structures all the time. You might realize that you already have tens of methods defined, but now you need a new one. You keep discovering new requirements, and you may want few more methods and then some more a few days later. Sometimes, you don't own the code, and it is not possible to add new members for some reason. What will you do in those situations?
The pattern matching gives you an alternative to handle this problem. The idea is simple but incredible. Deconstruct the object. I mean, when you create an object, you pass parameters to the object and construct it. I am talking about reverse procedure. Take the data structures out from the object. If required, deconstruct those structures as well. Then, it becomes easy to perform the necessary operations on those data structures. In a layman's language. If you can open a box and take individual items out, you can do a lot many things with those items. Right?

Scala pattern matching example

Let me give you an example. Assume you have a class to define a message.
The code for the class looks like this.

                                
    class Message(p_id:String, p_msg:String) {
        val id  = p_id;
        val msg = p_msg;
    }                                           
                         

We have two data elements. The id of the person sending the message, and the message text itself. Both items are string type. Since id is a string, it can be an email address, a mobile number, a Skype id or may be something else. In fact, anything that we can represent as a valid String may be a sender's id.
Now, assume you have got a list of Messages. It may be something like this.

                                
    val messageList = List(
        Message("tom@gmail.com","Message text 1"),
        Message("7742394590","Message text 2"),
        Message("8326192398","Message text 3"),
        Message("lisa@gmail.com","Message text 4"),
        Message("lisa@yahoo.com","Message text 5"),
        Message("harry@gmail.com","Message text 6")
    )                                       
                         

Some of them are email messages while others are SMS from a mobile phone. Now, we have a question to answer.
Do we have two successive emails from the same person on this list?
When I say the same person, I mean lisa@yahoo.com is the same as lisa@gmail.com. Can you create a function to answer this question?
How will you do it? Think about it?Let me give you a step by step logic.

  1. Take two messages from the list. (Deconstruct)
  2. Extract the sender's id from both messages. (Deconstruct)
  3. Check if both are email addresses. (Type test)
  4. If yes, ignore the domain part and extract only the username. (Deconstruct)
  5. Check if both usernames are same. (if Condition)
  6. If yes, return a positive answer. (Action)
  7. If no, remove the head of the list and repeat with the tail of the list. (Deconstruct and repeat)
  8. If you reach the end of this list, return a negative answer. (Action)

Do you agree with the steps? They are straight forward. You can easily write a function using Java or any other language. But that would be little ugly and clumsy. Pattern matching can give you a code that is concise, clean and easy to follow.
If you carefully investigate the logic, there is nothing other than deconstruction, type test, and action.Let me explain this.
The first step is to take two messages from the list. In fact, it is deconstructing your list into three parts. The first message, the second message, and the remaining messages.
Step 2 is again a deconstruction of a message into id and the message text.
Step 3 is a Type test. If we have a class for the email address, then we want to check if the given string is of an email address type.
Step 4 is once again deconstructing an email address to a username and domain name.
Step 5 is an if condition.
Step 6 is an action.
Step 7, this is again deconstructing your list into a head and the tail.
The point that I am trying to make is this.
You can sense a pattern matching solution when your logic is all about following things.

  1. Deconstructing objects
  2. Type checking
  3. Testing if conditions and taking Actions

That's what we have here in this requirement.
My function looks like this.

                                
    def testMessagePattern(l:List[Message]):String = {
        l match {
            case Nil => "Not found"
            case Message(EmailAddress(u1,d1),_) :: Message(EmailAddress(u2,d2),_) :: _ if(u1==u2) => u1 + " got two successive emails"
            case h::t => testMessagePattern(t)
        }
    }                                       
                         

Doesn't it look simple, concise and straightforward? It is hard to explain the code without going into lots of Scala language details. We need few more lines of code to make this work. So, I am skipping the detailed explanation for now. We will revisit this example and pick up few more examples when we talk about pattern matching and extractors in Scala language.
However, the complete code is listed below for your reference. The video tutorial executes the code and shows the result.

                                
    class Message(p_id:String, p_msg:String) {
        val id  = p_id;
        val msg = p_msg;
    }
                                        
        object Message{
            def apply(id:String, msg:String) = new Message(id,msg)
                                        
            def unapply(m:Message) :Option[(String,String)] =  {
                if (m == null) None
                else  Some(m.id, m.msg)
            }
        }
                                        
        object EmailAddress {
            def apply(uname: String, dname: String) = uname + "@" + dname
                                        
            def unapply(str: String): Option[(String, String)] = {
                val parts = str split "@"
                if (parts.length == 2) Some(parts(0), parts(1)) else None
            }
        }
                                        
        def testMessagePattern(l:List[Message]):String = {
            l match {
                case Nil => "Not found"
                case Message(EmailAddress(u1,d1),_) :: Message(EmailAddress(u2,d2),_) :: _ if(u1==u2) => 
                        u1 + " got two successive emails"
                case h::t => testMessagePattern(t)
            }
        }
                                        
        val messageList = List(
            Message("tom@gmail.com","Message text 1"),
            Message("7742394590","Message text 2"),
            Message("8326192398","Message text 3"),
            Message("lisa@gmail.com","Message text 4"),
            Message("lisa@yahoo.com","Message text 5"),
            Message("harry@gmail.com","Message text 6")
        )
                                        
        println(testMessagePattern(messageList))                                        
                         

Great. That's all about pattern matching for now.
Thank you for watching Learning Journal. Keep learning and Keep growing.


You will also like:


Kafka Core Concepts

Learn Apache Kafka core concepts and build a solid foundation on Apache Kafka.

Learning Journal

Pattern Matching

Scala takes the credit to bring pattern matching to the center.

Learning Journal

Apache Spark Introduction

What is Apache Spark and how it works? Learn Spark Architecture.

Learning Journal

Scala placeholder syntax

What is a scala placeholder syntax and why do we need it? Learn from experts.

Learning Journal

Higher Order functions

Scala allows you to create Higher Order functions as first class citizens.

Learning Journal