At this moment I do not have a personal relationship with a computer.
– Janet Reno
In Scala, partial functions are functions that are only defined for a subset of possible input values. They are typically defined using the PartialFunction
trait,
(Note: Do not confuse with Partially Applied Function)
The signature of the PartialFunction
trait looks like this:
trait PartialFunction[-A, +B] extends (A) => B
Note: The =>
symbol can be thought of as a transformer, and in this case, the (A) => B
can be interpreted as a function that transforms a type A
into a resulting type B
.
Note: the symbols +
and -
can be used as part of a parameter declaration to indicate whether the parameter is covariant (+
) or contravariant (-
) with respect to its type parameter. Refer covariant and contravariant parameters.
To define a partial function we have various approaches:
Approach 1 : Implement two methods namely isDefinedAt and apply:
val convertLowNumToString = new PartialFunction[Int, String] { val nums = Array("one", "two", "three", "four", "five") def apply(i: Int) = nums(i-1) def isDefinedAt(i: Int) = i > 0 && i < 6 } OR val convertLowNumToString => PartialFunction[Int, String] = (numInString: String) => { new PartialFunction[Int, String] { val nums = Array("one", "two", "three", "four", "five") def apply(i: Int) = nums(i-1) def isDefinedAt(i: Int) = i > 0 && i < 6 } }
Approach 2 : Another way to declare partial function is to use the case statement from the scala match case statement.:
val pf1: PartialFunction[Int, String] = {
case 1 => "one"
case 2 => "two"
}
val pf2: PartialFunction[Int, String] = {
case 3 => "three"
case 4 => "four"
}
Chaining Partial Functions:
Partial Function has two important methods: orElse
and andThen
.
For example if we want a method to be transformed an input Int
into an output returned a String
instead, it would be declared like this below:
PartialFunction[Int, String]
orElse
is a method that takes another partial function as an argument, and returns a new partial function that is the result of chaining the two functions together. The resulting function is defined for any input value that is defined by either of the two input functions.
Here’s an example of orElse
in action:
// Method declaration
override def orElse[A1 <: A, B1 >: B](that: PartialFunction[A1, B1])
pf = pf1.orElse(pf2)
println(pf(1)) // Output: one
println(pf(3)) // Output: three
println(pf.isDefinedAt(5)) // Output: false
In this example, pf1
and pf2
are two partial functions that are defined for certain input values of type Int
. We can create a new partial function pf
by calling the orElse
method on pf1
with pf2
as the argument. The resulting function is defined for any input value that is defined by either pf1
or pf2
, but not for any other values.
andThen
is a method that takes another function as an argument, and returns a new partial function that is the result of applying the input function to the output of the original function. The resulting function is defined for any input value that is defined by the original function.
Here’s an example of andThen
in action:
// Method declaration
override def andThen[C](k: B => C): PartialFunction[A, C]
val pf: PartialFunction[Int, String] = {
case 1 => "one"
case 2 => "two"
}
val f: String => String = s => s.toUpperCase
val pf2 = pf.andThen(f)
println(pf2(1)) // Output: ONE
println(pf2(2)) // Output: TWO
println(pf2.isDefinedAt(3)) // Output: false
In this example, pf
is a partial function that is defined for certain input values of type Int
. We can create a new function f
that takes a string and returns its uppercase version. We can then create a new partial function pf2
by calling the andThen
method on pf
with f
as the argument. The resulting function applies f
to the output of pf
for any input value that is defined by pf
.