Functions and methods are essential components of programming. They help us to process input and provide us with output.
In this blog, we will learn about partial functions through examples in Scala.
Note: Do not confuse it with partially defined functions.
What are partial functions?
In English, “partial” means something which is not complete. Similarly, a partial function is a function applicable only for a subset of the input values over which we define it.
Let’s understand this through an example where we create a function to perform Integer division.
val divide: (Int, Int) => Int = (num: Int, den: Int) => num / den
The above divide function is a partial function as it is not defined for all the possible int values of the denominator i.e. 0 in this case.
PartialFunction trait in Scala
PartialFunction is a trait present in the Scala standard library. As per Scala docs:
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.
trait PartialFunction[-A, +B] extends (A => B) { ...... }
Let’s break down the definition part by part.
Firstly, it says a PartialFunction is a unary function i.e. it takes only one argument and of type A.
Secondly, we do not define the function is for all the possible values of Type A.
Lastly, we can check whether the function is defined for a particular value or not using the isDefinedAt function.
Writing Partial Functions in Scala
There are 2 ways in which we can rewrite our divide function using PartialFunction:
val betterDivide: Int => PartialFunction[Int, Int] = (num: Int) => {
new PartialFunction[Int, Int] {
override def isDefinedAt(den: Int): Boolean = den != 0
override def apply(den: Int): Int = num / den
}
}
There is another way in which we can define the above division function.
The second version is less verbose than the first one.
val betterDivide2: Int => PartialFunction[Int, Int] = (num: Int) => {
case den: Int if den != 0 => num / den
}
Utilizing the above partial functions
This is how we invoke the above 2 variants of the function:
// Invoking First Version
val divide4By: PartialFunction[Int, Int] = betterDivide(4) // 4: Numerator
if(divide4By.isDefinedAt(2)) println(divide4By(2)) // 2: Denominator
else println("Invalid Division")
// OUTPUT: 2
if(divide4By.isDefinedAt(0)) println(divide4By(0)) // 0: Denominator
else println("Invalid Division")
// OUTPUT: Invalid Division
// Invoking Second Version
val divide6By = betterDivide2(6)
if(divide6By.isDefinedAt(2)) println(divide6By(2))
else println("Invalid Division")
// OUTPUT: 3
if(divide6By.isDefinedAt(0)) println(divide6By(0))
else println("Invalid Division")
// OUTPUT: Invalid Division
Chaining Partial Functions
The Scala standard library also provides some useful functions in the PartialFunction trait:
1. orElse
// Method declaration
override def orElse[A1 <: A, B1 >: B](that: PartialFunction[A1, B1])
// Usage
partialFunction1 orElse partialFunction2
If partialFunction1’s isDefined method returns false then partialFunction2 is executed
2. andThen:
// Method declaration
override def andThen[C](k: B => C): PartialFunction[A, C]
// Usage
val divide6AndIncrement = betterDivide2(6) andThen(_ + 1)
if(divide6AndIncrement.isDefinedAt(2)) println(divide6AndIncrement(2))
else println("Invalid Division")
// OUTPUT: 4
andThen method takes a function and applies it to the result of the partial function.
Conclusion
After going through this blog the readers hopefully understand what are partial functions. Many Scala libraries like the collection library and other APIs like Akka make use of Partial functions.
For example, the Receive type in Akka Actors is nothing but a partial function from Any => Unit.
So I hope the next time we come across Partial Functions we will be less intimidated by it and would be able to make better sense of the code.