What makes pattern matching and “implicits” in Scala so powerful?
Learn how to enable functional behavior in Scala code.
In my online class, Scala: Beyond the Basics, I discuss two very important aspects to functional programming in Scala. The first is pattern matching, which is the ability to take a pattern of how things are constructed and tear them apart and make decisions based on the parts that make up the item. For example, if I were to create a student registration system for a middle school, I may want to deconstruct the student (not literally) to find out the student’s last name and then put them into the corresponding line (i.e., A-F, G-N, O-Z).
Another aspect I talk about in the course are implicits. Implicits can be a powerful ally in functional programming, but, when used incorrectly, they can turn on you and make debugging your code difficult. With implicits, you can add methods to objects that already exist, establish conversion strategies, and even resurrect parameterized types that have been erased at runtime.
Let’s take a deeper look into what makes both of these features so powerful for Scala developers…
Pattern Matching
You can think of pattern matching as an if
or a switch
statement on functional steroids. With that said, pattern matching has always been something enjoyed by functional programmers.
Why pattern matching? For one reason, pattern matching is not only for primitive types or String
like a switch
statement in Java, it is for every conceivable object. If you come from a Java background, this is already a win. Pattern matching can be programmed to extract values given your own logic or use some of the predefined logic as already developed in the language.
This goes further than a standard regular expression matching from a String
. Whereas regular expressions match on a character stream, pattern matching matches on objects themselves. Pattern Matching tears apart the composable parts of an object so that you can analyze those components and make decisions based on those constituent parts. If those constituent parts have no relevance to your decision, you can just ignore them to get what you need.
Take a look at the following example in Scala:
def second[A](ls:List[A]):Option[A] = { ls match { case Nil => None case a :: Nil => None case a :: xs => Some(xs.head) } }
Here we have a method labeled second
that accepts a List
and returns an Option
of type A
. One of the Option
reasons for existence is to avoid null
. An Option
is a functional programming way to say, “Yes, I have an answer and here it is, or no, I don’t have an answer and you’ll get nothing further from me.”
The ls match
is how we enter into pattern matching in Scala. From there we have subsequent case
statements that in turn will determine if the given pattern will indeed match what we gave the match
, and in this case it is ls
.
The first case is Nil
which means that if ls
is an empty List
, then we will return None
which is our way of saying that no, there is no second
in this List
.
The second case is a {two-colons} Nil
which by its pattern means that there is only one element since Nil
means the end of the list. The element will be represented by a
.
The third case is a {two-colons} xs
which by this last pattern means that a
will represent the first element and the xs
will be the remainder of the list. If you are familiar with old style LISP this will be analogous to car
and cdr
.
Implicits
Another powerful feature in Scala is implicits
. However, implicits
can be a double-edged sword. Some aspects are pretty amazing, such as how they meld with type classes and add additional behaviors to preexisting classes, but other aspects can make Scala programs rather hard to read and debug. Regardless, implicits
can be a tremendous tool if used in the right way.
implicits
can be thought of as invisible hash maps that have a key as a type or class
and a value of an object or a function. These hash maps are bound to a scope, particularly where they are either created or imported.
Remember the operative word is “scope”. If you set an implicit
at a particular scope it will be available at that scope.
This is how an implicit
is established, in this case using an Int
:
implicit val a = 100
Whatever scope this is in, we have an Int
that is tied to 100
. Whenever we need that Int
, we can ask for it either in a method declaration, like in the following example:
def calculateWeeklyRate(hours:Int)(implicit rate:Int) = { hours * rate }
Or the previous example can be called in the following manner:
calculateWeeklyRate(40) //4000
Notice that the rate
is not set, that is what is meant by implicit
it will obtain the rate from the environment where an Int
type is bound to an object, in this case, that object is a 100
.
We can also just ask for it using the method implicitly
and get the object that corresponds to that type whenever we need it:
val result = 300 * implicitly[Int] //30000
Obviously, there is much more to both pattern matching and implicits
, but this should be enough to whet your appetite. If you’d like to learn more, register for one of my online training courses on April 11-12 and May 16-17. You’ll receive some solid grounding to explore these two features in even greater depth.