The Scala Chronicles: PartialFunction

Scala Extractors
Reading Time: 3 minutes

If you’re new to programming in Scala(like I am), you must have heard someone or the other talking about how awesome and powerful the case keyword, Partialfunction and pattern matching in Scala are. It got me wondering about why people love them that much. So, to unravel this mystery around case, I went on an adventure to find out all there is to know about them(And boy! was it amazing?!) and I’d like to share my findings with you. So, Without further adieu, I bring to you the following:

  1. What are Functions?
  2. Total Functions and PartialFunctions
  3. The PartialFunction trait.

What are Functions?

Most simplistically, a function is a mapping or a transformation of an input to an output. if it helps, you can think of it as being a room with two doors; one to just enter the room(that’s where the inputs come in from) and the other for exit(where the output comes out).

Scala differentiates between methods(associated with some object) and functions(associated with some type). But that’s a story for another day. The most simple way of defining a function in Scala is by the way of function literals. For example:

object Solution extends App
val someFunctionLiteral: Int => Int = (x:Int) => x*2
view raw Solution.scala hosted with ❤ by GitHub

If you try to disassemble the class file, you’d see that the the JVM creates a method in the Solution class `someFunctionLiteral` with the Scala.Function1 as its return type. Pretty amazing, right? 

Total Functions vs PartialFunctions

Total Function is a just synonym for a function(written f: X->Y). The adjective total is written just to distinguish them from partial functions. A function is just another mathematical object that produces an output when given an input – it could be a number, a vector, or anything that can exist inside a set of things. whereas, A partial function generalises the concept of a function f: X -> Y,  by not forcing f to map every element of X to an element of Y.

The PartialFunction trait

The official docs define Partial Function as:

A partial function of type `PartialFunction[A, B]` is a unary function where the domain does not necessarily include all values of type `A`. The function isDefinedAt allows to test dynamically if a value is in the domain of the function.

Let’s try to break this down bit by bit, shall we?

The PartialFunction trait in Scala is a pretty nifty feature, if you ask me. It lets you create functions where the domain X doesn’t necessarily map to every element of Y and not just that, it lets you dynamically test if a value is in the domain of the function.

The trait extends (A) => B, which is just another way of saying that it extends scala.Function1[A,B]. Also, PartialFunction is a trait + companion object combo. The companion object contains all the methods that we use to apply or chain PFs together. There are two primary ways to use PartialFunction in the code:

  1. Using the new keyword.
  2. Using case keywords.

Taking the longer, more verbose, route with the new keyword

I like Scala, don’t get me wrong here, but there’s something about Java that I can’t get enough of. So, the first way of creating a PartialFunction is by providing the Java-esque anonymous class representation of the trait. To be able to do that, you must provide an implementation for the following methods:

  1. scala.Function1’s apply method.
  2. scala.PartialFunction’s isDefinedAt method.

Like in the example below,

val deepThoughtProcessor = new PartialFunction[Int, Int] {
override def isDefinedAt(number: Int): Boolean = number == 21
override def apply(number: Int) = number * 2
val deepThoughQuery = deepThoughtProcessor isDefinedAt 22 // res0 = false
val lifeUniverseEverything = deepThoughtProcessor apply 21 //res1 = 42

deepThoughtProcessor is a PartialFunction that is defined only at number 21, to be able to calculate the answer to the ultimate question of life(42). One of the powerful things about PartialFunction is that it provides us with the ability to query the function, to see if it is defined at that particular argument or not, before actually invoking the function using the isDefinedAt method(Pretty cool! isn’t it?!).

Staying true to the roots with the case keyword

Here’s where Scala gets interesting, a bunch of cas inside a block is one way to create an anonymous function. Let me show you!

val deepThoughtProcessor: PartialFunction[Int, Int] = {
case number:Int if number == 21 => number*2
deepThoughtProcessor isDefinedAt 22 // res0 = false
deepThoughtProcessor(21) // res1 = 42
/* Now, if you still try to invoke deepThoughtProcessor with the argument
* it's not defined at you'd get a MatchError
deepThoughtProcessor(22) //MatchError

Here’s where this approach of creating PartialFunctions outshines the previous one. In the example before this one, if you try to pass an argument on which deepThoughtProcessor is not defined at(44), it would give in and return the result(88). But it’s not the same with the case approach, it would simply give you a MatchError(You don’t mess with Deep Thought).

That’s it for this blog then(getting a bit crowded in here, ain’t it?). In my next blog, I’d talk more on how PartialFunction defined using case keywords can lie and how you could take advantage of chaining PartialFunction together.!

Until next time! 🍺