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.
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.
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.
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.
- Take two messages from the list. - Extract the list
- Extract the sender's id from both of the messages. - Extract the message
- Check if both of them are email addresses. - Type test
- If yes, ignore the domain part and extract only the username. - Extract the email address
- Check if both usernames are same. - if Condition
- if yes, return a positive answer. - Action
- if no, remove the head of the list and repeat with the tail of the list. - Extract the list and repeat
- 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.
- Extracting objects
- Type checking
- Testing if conditions and taking Actions
That is what we have here in this requirement. A complete code example is here.
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.
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