Function composition
Function composition is a technique to compose a function using multiple functions. This will allow us to achieve the following.
- Keep the functions follow single responsibility principle
- Prevents mutation within the function
- Making the function easily testable
- Makes the program safer.
If you would like to learn more about function composition or functional programming, you can refer the following series
Let’s take a look on how a function composition is done on a usual way. For the sake of simplicity, I’m going to consider a simple square and triple example.
fun square(value: Int): Int = value * value
fun triple(value: Int): Int = 3 * value
fun compose(
square: (Int) -> Int,
triple: (Int) -> Int
): (Int) -> Int = { value -> square(triple(value)) }
val squareOfTriple: (Int) -> Int = compose(::square, ::triple)
println(squareOfTripleOp(3)) // output -> 81
KotlinThis above composition is all well and good. But you may want to replace the property function squareOfTriple
with an actual function. One such scenario is when we need to overload the function. Such as,
object Math {
fun calculate(value: Int): Int = square(triple(value))
// we obviously don't need a new function just for this Long type
// consider this example only for understading the situation
fun calculate(value: Long): Int = square(triple(value.toInt()))
private fun square(value: Int): Int = value * value
private fun triple(value: Int): Int = 3 * value
}
KotlinFor such situations, we cannot use functional references, as our function calcualte()
is no longer a property function + we cannot overload the property based on return value. So, we need to use functions for sure.
if we want to use reusable composition with functional references, we can avoid the use of square(triple(value))
like the example below
Creative compositon using extensions
The function composition mentioned in the previous section is perfectly fine. But let’s get a bit more creative. To make you understand the final result, I’m going to go ahead and break the spoilers. Our final outcome should be like the following
object Math {
fun calculate(value: Int): Int =
::square.then(::triple)(value)
fun calculate(value: Long): Int =
::square.then(::triple)(value.toInt())
// we can chain as much as we can ->
// ::square.then(::triple).then(::square)
}
KotlinInteresting right? Let’s get to work.
To achieve this, we need to create an extension function for the function of type ‘(?) -> ?’ of name ‘then’ which composes our square of triple for us
fun <T, U, V> ((T) -> U).then(other: (U) -> V): (T) -> V =
{ other(this(it)) }
Kotlinwith the above extension function then()
we can get the following composition ::square.then(::triple)(value)
But why stop here? Let’s take our creativity a one step further. As before, let’s set our expectation
object Math {
fun calculate(value: Int): Int =
(::square then ::triple)(value)
fun calculate(value: Long): Int =
(::square then ::triple)(value.toInt())
}
KotlinIf you have wrestled with extension functions in kotlin, you know where I’m getting at. Yes, you are right! the infix
function
infix fun <T, U, V> ((T) -> U).then(other: (U) -> V): (T) -> V =
{ other(this(it)) }
KotlinCreative compositon using operator overloading
Kotlin allows us to provide implementations for a predefined set of operators on our types. These operators have fixed symbolic representation (like + or *) and fixed precedence. To implement an operator, we provide a member function or an extension function with a fixed name, for the corresponding type, i.e. left-hand side type for binary operations and argument type for unary ones. Functions that overload operators need to be marked with the operator modifier. (source)
Now that we know what overloading an operator means, let’s get creative and put that in motion. Asusal, here’s our expectation
object Math {
fun calculate(value: Int): Int =
(::square + ::triple)(value)
fun calculate(value: Long): Int =
(::square + ::triple)(value.toInt())
}
KotlinTo achieve this, we need to create an extension function for the function of type ‘(?) -> ?’ by overloading the operator plus ‘+’ which composes our square of triple for us
operator fun <T, U, V> ((T) -> U).plus(other: (U) -> V): (T) -> V =
{ other(this(it)) }
KotlinBy overloading the plus
operator, we were able to achieve our expectation. Feel free to overload your favorite operator like rangeTo
etc.
Final thoughts
Personally, as much as overloading an operator is fun for this usecase, this might confuse people a lot and it might send wrong signal for overloading the operator. I’m comfortable to use extension function. Rest, I’ll leave the decision to you!
I hope you found this piece like an art to admire! If you think you have learned something new or if you like this series or you want to boost my morale, please share the post. Thanks a lot for reading this article and thanks a lot for your time!