Scala: Partial functions

Learn
3 min readJan 5, 2022

Total functions are functions which promise to fulfill it’s functionality for every legal input that it takes.
I understand the term “Partial function” in contrast to this definition of a total function. Partial function is a function that is explicit in saying that it does not fulfill the functionality for the entire range of inputs that it can take, only partially, and it also lets the user know whether the function is defined at the particular input or not.

This range of inputs essentially refers to the type of the arguments.

A partial function has a method named isDefinedAt which would indicate whether the function is defined for the particular value.

isDefinedAt
- For a particular input, if isDefinedAt returns true, then the function would work as expected for the particular input.

Principle of least astonishment (POLA)
One of the principles of good API design is that it should provide minimum surprises to it’s users. Partial function is a technique that aligns well with POLA. The API designer is explicitly letting the API user know that there are certain values within the type of the argument the function accepts that would not work well.

val splitHundredToParts = new PartialFunction[Int, Int] {
def isDefinedAt(x : Int) = !(x == 0 )
def apply(x : Int) = 100 / x
}

The API user now has the facility to invoke this function carefully, and can decide to put in a valid default that work for this particular API user. Another API user might have wanted another default.

def runSplit(n : Int) = { if(splitHundredToParts.isDefinedAt(n)) 
splitHundredToParts(n)
else
100
}
println(runSplit(5))
println(runSplit(0))
20
100

If you want to be a good API designer, consider partial functions to achieve POLA, and if you want to leverage a thought out partial function as a consumer, make conscious choices about the range that is not supported.

Chaining partial functions with orElse

Let us say you are dealing with a JSON response from a third party API and you are interested in one specific value from with the JSON structure. The formats of the JSONs that come through have multiple optional portions, and this particular value can be buried in various segments based on the flavor of the response.

If this scenario resonates, you might have had to write multiple structure parsing pieces in a single method. This makes the function difficult to manage.

Partial function can offer a solution to this scenario with the orElse feature. You write one partial function per scenario, say you write three partial functions for the three variants of json formats. Now you define the all scenario handling function using the orElse operator to chain these three partial function variants.

val toStringOne = new PartialFunction[Int, String] {
def isDefinedAt(x : Int) = x <= 3
def apply(x : Int) = x match {
case 1 => "One"
case 2 => "Two"
case 3 => "Three"
}
}
val toStringTwo = new PartialFunction[Int, String] {
def isDefinedAt(x : Int) = x > 3 && x <= 6
def apply(x : Int) = x match {
case 4 => "Four"
case 5 => "Five"
case 6 => "Six"
}
}
val toStringThree = new PartialFunction[Int, String] {
def isDefinedAt(x : Int) = x > 6 && x <=10
def apply(x : Int) = x match {
case 7 => "Seven"
case 8 => "Eight"
case 9 => "Nine"
case 10 => "Ten"
}
}
val toStringAll = toStringOne orElse toStringTwo orElse toStringThreefor(i <- 1 until 11)println(toStringAll(i))

This is a simpler scenario to demonstrate and is self explanatory. Three functions are defined which convert number to words for the three segments of range from 1 to 10, and then you combine the three into one single function.

The output would be as below.

One
Two
Three
Four
Five
Six
Seven
Eight
Nine
Ten

These smaller functions are easier to maintain and reason about than having all of the disjoint independent flavors managed and maintained as one piece of functionality.

--

--