Partial functions

A partial function (not to be confused with partial applied function) is a function that is not defined for every possible value of its parameter type. In contrast, a total function is a function that is defined for every possible value.

Let's have a look at the following example:

fun main(args: Array<String>) {
val upper: (String?) -> String = { s:String? -> s!!.toUpperCase()} //Partial function, it can't transform null

listOf("one", "two", null, "four").map(upper).forEach(::println) //NPE
}

The upper function is a partial function; it can't process a null value despite the fact that null is a valid String? value. If you try to run this code, it will throw a NullPointerException (NPE).

Arrow provides an explicit type PartialFunction<T, R> for partial functions of type (T) -> R:

import arrow.core.PartialFunction

fun
main(args: Array<String>) {
val upper: (String?) -> String = { s: String? -> s!!.toUpperCase() } //Partial function, it can't transform null

val partialUpper: PartialFunction<String?, String> = PartialFunction(definetAt = { s -> s != null }, f = upper)

listOf("one", "two", null, "four").map(partialUpper).forEach(::println) //IAE: Value: (null) isn't supported by this function
}

PartialFunction<T, R> receives a predicate (T) -> Boolean as the first parameter which must return  true if the function is defined for that particular value. A PartialFunction<T, R> function extends from (T) -> R, therefore it can be used as a normal function.

In this example, the code still throws an exception but now of type IllegalArgumentException (IAE), with an informative message.

To avoid getting exceptions, we must transform our partial function into a total one:

fun main(args: Array<String>) {

val upper: (String?) -> String = { s: String? -> s!!.toUpperCase() } //Partial function, it can't transform null

val partialUpper: PartialFunction<String?, String> = PartialFunction(definetAt = { s -> s != null }, f = upper)

listOf("one", "two", null, "four").map{ s -> partialUpper.invokeOrElse(s, "NULL")}.forEach(::println)
}

One option is to use the invokeOrElse function that returns a default value in case the value s isn't defined for this function:

fun main(args: Array<String>) {

val upper: (String?) -> String = { s: String? -> s!!.toUpperCase() } //Partial function, it can't transform null

val partialUpper: PartialFunction<String?, String> = PartialFunction(definetAt = { s -> s != null }, f = upper)

val upperForNull: PartialFunction<String?, String> = PartialFunction({ s -> s == null }) { "NULL" }

val totalUpper: PartialFunction<String?, String> = partialUpper orElse upperForNull

listOf("one", "two", null, "four").map(totalUpper).forEach(::println)
}

The second option is to create a total function using several partial functions with the function orElse:

fun main(args: Array<String>) {
val fizz = PartialFunction({ n: Int -> n % 3 == 0 }) { "FIZZ" }
val buzz = PartialFunction({ n: Int -> n % 5 == 0 }) { "BUZZ" }
val fizzBuzz = PartialFunction({ n: Int -> fizz.isDefinedAt(n) && buzz.isDefinedAt(n) }) { "FIZZBUZZ" }
val pass = PartialFunction({ true }) { n: Int -> n.toString() }

(1..50).map(fizzBuzz orElse buzz orElse fizz orElse pass).forEach(::println)
}

Through the isDefinedAt(T) function we can reuse the internal predicate, in this case, to build the condition for fizzBuzz. When used in a chain of orElse, the declaration order takes precedence, the first partial function that is defined for a value will be executed, and the other functions down the chain will be ignored.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.114.28