Function Composition
This is the third part of the functional programming series. If you have missed the previous one, start here.
Series pit stops
- f(1) — Basics, Understanding Functions
- f(2) — Functions in Kotlin, Pure functions
- f(3) — Function composition (you are here)
- f(4) – Currying function
In this pit stop, we’ll briefly examine how to compose functions.
Pre-requisites
- Basic programming knowledge
- School mathematics
- Previous pit stop
Mathematical Composition
In our first pit stop, we understood how to compose two function or sets mathematically. To brush up quickly, I’m including a small gist here again. If you are confident, please move to next section (Kotlin Composition).
Functions are like legos, meaning — you can use them to build or compose other functions. In other words, function composition is an operation on functions to produce a new function.
For example,
- Consider function1: f(x) = x + 1
- Consider function2: g(x) = 2x
- Our aim, to compose a function h(x) using f(x) and g(x)
The composition of a function f over g is usually denoted as f o g. This is pronounced as f of g or f round g or can be denoted as f(g(x)). Which gives us,
h(x) = f o g = f(g(x) = f(2x) = (2x) + 1 => 2x + 1
KotlinThe value of g(x) is substituted in place of x in f(x) ☝️
Hence, f o g = 2x + 1
Fun fact: You can also compose g o f, but f o g and g o f are not equal operations. they may result in the same sometimes though. If we compose g round f for the above functions,
g o f = g(f(x)) = 2(x+ 1) = 2x + 2
KotlinHence, for the mentioned f(x) and g(x), f o g != g o f
Kotlin Composition
Let’s try to compose the same functions in Kotlin
fun f(x: Int) = x + 1
fun g(x: Int) = 2 * x
println(f(g(2)) // output -> 5
KotlinIf you think that’s how the function composition is done using functions, then you are wrong.
A function composition is like a binary operation on functions, just like adding integers or numbers, composition is also an operation on functions.
fun f(x: Int) = x + 1
fun g(x: Int) = 2 * x
// function composition
fun compose(f: (Int) -> Int, g: (Int) -> Int): (Int) -> Int = { x -> f(g(x)) }
KotlinThe compose function, takes in two functions as parameter where each of them have an integer as a parameter and as a return value and it also returns a function of integer as a parameter and as a return value.
We now use the compose function to compose f and g
val fog = compose(::f, ::g) println(fog(2)) // output -> 5
KotlinFun fact: Functions in Kotlin supports functional references (::). We use this operator to access the reference of a function. Just like how we pass value by reference. If the function is of value type or property, then reference is not needed.
Polymorphic function composition
The compose function that we created is restricted to one type. What if we need to compose a function of generic type?
fun r1(x: Boolean): Int = if(x) 1 else 0
fun r2(x: Int): Boolean = if(x == 0) false else true
fun <T, U, V>compose(f: (U) -> V, g: (T) -> U): (T) -> V =
{ f(g(it)) }
val r1of2 = compose<Int, Boolean, Int>(::r1, ::r2)
println(r1of2(2)) // output -> 1
KotlinIt might be bit overwhelming to see the generic composition. You are not alone. Take a moment to write such compositions with different types to understand better.
Advantages
- Composing helps us to build re-useable methods to compute functions
- We can build complex functions from a simple building blocks
- We can test all simple functions easily
TLDR;
- Function composition is like a binary operation on functions
- Polymorphic function composition allows us to reuse the function to any 2 functions of generic types
- Using composition, we can keep the functions small and testable
Additional References
If you think you have learned something new or if you like this series or you want to boost my morale to publish the next pit stop fast, please share the post. Thanks a lot for reading this article. Stay tuned!!