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:
- What are Functions?
- Total Functions and PartialFunctions
- 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:
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 functionhttps://www.scala-lang.org/api/2.12.8/scala/PartialFunction.html
isDefinedAtallows 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:
- Using the new keyword.
- 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:
- scala.Function1’s apply method.
- scala.PartialFunction’s isDefinedAt method.
Like in the example below,
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!
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! 🍺