Partial & Partially Applied Functions in Scala
Partial Functions
Pattern Matching in Scala can be extended to create Partial functions, a unary function that does not support every possible value that meets the input type. Partial functions are defined only for certain domain of input values. For example, a function that returns the Square root of the input number will not work if the input number is negative.
val squareRoot: PartialFunction[Double, Double] = {
case x if x >= 0 => Math.sqrt(x)
}
The above squareRoot
function is defined only for positive numbers, and returns the square root of the number. In case of negative numbers, the function returns scala.MatchError
runtime exception.
A partial function can also be queried to determine if it can handle a particular value. The function isDefinedAt allows testing dynamically if a value is in the domain of the function.
squareRoot.isDefinedAt(2) shouldEqual true
squareRoot.isDefinedAt(-2) shouldEqual false
Partial functions can be chained together using orElse or andThen
val positive: PartialFunction[Int, Int] = {
case x if x >= 0 => x
}
val odd: PartialFunction[Int, Boolean] = {
case x if x % 2 == 1 => true
}
val even: PartialFunction[Int, Boolean] = {
case x if x % 2 == 0 => true
}
val evenCheck: PartialFunction[Int, Boolean] = positive andThen even
val oddCheck: PartialFunction[Int, Boolean] = positive andThen odd
Since the resulting chained function is also Partial, isDefined can be applied which checks the input against the domain of each partial function.
evenCheck.isDefinedAt(-2) shouldEqual false
evenCheck.isDefinedAt(2) shouldEqual true
This feature of Partial function becomes useful when implementing a validation system. If there are series of checks implemented to verify if the input data meets certain guidelines.
val finalCheck = check1 andThen check2 andThen check3 ...
This solution is also easily extendible as new checks are implemented they are added to the finalCheck
, and same in the case of removal.
Scala allows Partial functions to be applied to collections
val greaterThan20: PartialFunction[Any, Int] = {
case i: Int if i > 20 => i
}List(1, 45, 10, "blah", true, 25) collect greaterThan20
shouldEqual List(45, 25)
The collect method takes a PartialFunction as argument and maps the values defined for this partial function over it, skipping those outside the definition domain.
Partially Applied Functions
In functional programming languages, a call to a function that has parameters can also be stated as applying the function to the parameters. When a function is called with all the required parameters, it has fully applied the function to all of the parameters. But when only a subset of the parameters to the function is passed, the result of the expression is a Partially Applied function. Scala does not throw an exception when you provide fewer arguments to function, it simply applies them and returns a new function with rest of arguments which need to be passed.
val divide = (num: Double, den: Double) => {
num / den
}val halfOf: (Double) => Double = divide(_, 2)halfOf 20 shouldEqual 10
Since we know half of any number is nothing but a simple division operation with denominator always fixed to be 2, halfOf
is created by partially applying the divide
function. _
is used as a placeholder for all the parameters not passed to the function.
Partially applied functions are easily confused to be the same thing as Currying in Scala. Both Currying and Partially applied functions reduce the arity of the function, but currying extends this concept of partially applying the function to the next level. Currying is the process of decomposing a function that takes multiple arguments into a sequence of functions, each with a single argument.
val curriedDivide: (Double) => (Double) => Double = divide.curried
The curriedDivide
function has (Double) => (Double) => (Double)
type, denoting the decomposition of the function divide
into two functions, each taking one argument from the divide
function in the order they were declared as parameters. Partially applying the curriedDivide
results in the same halfOf
function.
val halfOf: (Double) => Double = curriedDivide(_)(2)
halfOf(20) shouldEqual 10
The benefit of Currying and Partially Applied function is the ability to create specialized functions based on general functions without introducing new code keeping the code free of duplication.