Pattern Matching

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

What is pattern matching?

When we talk about pattern matching, the first thing that comes to our mind is a string matching or a regular expression. However, 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. You got an object, and you want to test it for following possibilities.
Does it match with a String Object?
Does it match with an Integer Object?
Does it match with a Double Object?
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 _         =>  "Oops! Something Else"
        }
    }                
                                    

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 instanceOf tests. Moreover, when the test succeeds, you need to do a cast. However, Scala pattern matching gives you a convenient alternative which looks like Java's switch statement. However, instead of matching numbers, that is what a switch does, you are matching object types. The Scala's pattern matching also binds the value on a pattern match.
In the above 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 do not 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.


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. Once encapsulated, we do not look at the data structures within the object. We just use methods. When we need new functionality or a feature, we add new methods to the object definition. 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 do not 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.

Extractors

The idea is to Extract the values from an object. The notion of Extractor is simple but incredible. 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, Extract 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.
Let take an example to understand the Extractor.
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 and the message text itself. Both of these items are string type. Since id> is a string, it can be an email address, a mobile number, a Skype id or maybe 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. - Extract the list
  2. Extract the sender's id from both of the messages. - Extract the message
  3. Check if both of them are email addresses. - Type test
  4. If yes, ignore the domain part and extract only the username. - Extract the email address
  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. - Extract the list and repeat
  8. If you reach the end of this list, return a negative answer. -Action

Do you agree with the steps? They are straightforward. You can easily write a function using Java or any other language. However, 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 Extraction, type test, and action.
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. Extracting objects
  2. Type checking
  3. Testing if conditions and taking Actions

That is what we have here in this requirement. A complete code example is here.


                                        
    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
        }
    }                
                                    

We have a class to define a message. Then we have an object for the message. The object defines an apply method to construct the message object. At the same time, we also have an unapply method to Extract the message object. Similarly, we have an object for email that can construct and Extract the email address.
You can use the below code to test the above example.

                                        
    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))                    
                                    

Keep reading for more interesting functional concepts.

Read More

Pure Functions | Referential Transparency | Benefits of pure functions | First class functions | Higher order function | Anonymous functions | Immutability | Tail Recursion | Expressions in Scala | Lazy Evaluations | Pattern Matching | Closures

By Prashant Pandey -


You will also like:


Pure Function benefits

Pure Functions are used heavily in functional programming. Learn Why?

Learning Journal

Apache Spark Introduction

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

Learning Journal

Referential Transparency

Referential Transparency is an easy method to verify the purity of a function.

Learning Journal

Scala Variable length arguments

How do you create a variable length argument in Scala? Why would you need it?

Learning Journal

Free virtual machines

Get upto six free VMs in Google Cloud and learn Bigdata.

Learning Journal