One big part of functional programming as a concept is to use functions in the same way that we use any other type—as values, parameters, returns, and so on. One thing that we can do with other types is to take them as construction blocks to build other types; the same concept can be applied to functions.
Function composition is a technique to build functions using existing functions; similar to Unix pipes or channel pipelines, the result value of a function is used as a parameter for the next one.
In Arrow, function composition comes as a set of the infix extension functions:
Function |
Description |
compose |
Takes the result of invoking the right-hand function as the parameter for the left-hand function. |
forwardCompose |
Takes the result of invoking the left-hand function as the parameter for the right-hand function. |
andThen |
Is an alias for forwardCompose. |
Let's compose some functions:
import arrow.syntax.function.andThen
import arrow.syntax.function.compose
import arrow.syntax.function.forwardCompose
import java.util.*
val p: (String) -> String = { body -> "<p>$body</p>" }
val span: (String) -> String = { body -> "<span>$body</span>" }
val div: (String) -> String = { body -> "<div>$body</div>" }
val randomNames: () -> String = {
if (Random().nextInt() % 2 == 0) {
"foo"
} else {
"bar"
}
}
fun main(args: Array<String>) {
val divStrong: (String) -> String = div compose strong
val spanP: (String) -> String = p forwardCompose span
val randomStrong: () -> String = randomNames andThen strong
println(divStrong("Hello composition world!"))
println(spanP("Hello composition world!"))
println(randomStrong())
}
To build the divStrong: (String) -> String function, we compose div:(String) -> String and strong:(String) -> String. In other words, divStrong is equivalent to the following code snippet:
val divStrong: (String) -> String = { body -> "<div><strong>$body</div></strong>"}
For spanP:(String) -> String, we compose span:(String) -> (String) and p:(String) -> String as follows:
val spanP: (String) -> String = { body -> "<span><p>$body</p></span>"}
Notice that we are using the same type (String) -> String, but any function can be composed if it has the right return type that the other functions need.
Let's rewrite our Channel pipeline example with function composition:
data class Quote(val value: Double, val client: String, val item: String, val quantity: Int)
data class Bill(val value: Double, val client: String)
data class PickingOrder(val item: String, val quantity: Int)
fun calculatePrice(quote: Quote) = Bill(quote.value * quote.quantity, quote.client) to PickingOrder(quote.item, quote.quantity)
fun filterBills(billAndOrder: Pair<Bill, PickingOrder>): Pair<Bill, PickingOrder>? {
val (bill, _) = billAndOrder
return if (bill.value >= 100) {
billAndOrder
} else {
null
}
}
fun warehouse(order: PickingOrder) {
println("Processing order = $order")
}
fun accounting(bill: Bill) {
println("processing = $bill")
}
fun splitter(billAndOrder: Pair<Bill, PickingOrder>?) {
if (billAndOrder != null) {
warehouse(billAndOrder.second)
accounting(billAndOrder.first)
}
}
fun main(args: Array<String>) {
val salesSystem:(Quote) -> Unit = ::calculatePrice andThen ::filterBills forwardCompose ::splitter
salesSystem(Quote(20.0, "Foo", "Shoes", 1))
salesSystem(Quote(20.0, "Bar", "Shoes", 200))
salesSystem(Quote(2000.0, "Foo", "Motorbike", 1))
}
The salesSystem: (Quote) -> Unit function is very complex in its behavior, but was built using other functions as building blocks.