As an Android developer in Kotlin, I often see devs asking why they should use lambdas and what is the benefit of using them. So let me share the concept of functional composition and its benefits with you.
However before we start with the code, let’s gain some theoretical background. Functional composition, or so called function composition, is a mathematical concept.
In mathematics, function composition is an operation that takes two functions
From Wikipedia: https://en.wikipedia.org/wiki/Function_compositionf
andg
and produces a functionh
such thath(x) = g(f(x))
. In this operation, the functiong
is applied to the result of applying the functionf
tox
.
Why this is so important you will see later. For now the important part is that we have methods that depend on other methods results.
Example
If you are Android developer, you might have seen this:
class Adapter(private val clickListener: (Model) -> Unit) : RecyclerView.Adapter<Adapter.Holder>() {
private val data: MutableList<Model> = mutableListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder =
Holder(ModelView(parent.context), clickListener)
override fun getItemCount(): Int = data.size
override fun onBindViewHolder(holder: Holder, position: Int): Unit =
holder.bind(data[position])
inner class Holder(view: View, listener: (Model) -> Unit) : RecyclerView.ViewHolder(view) {
init {
view.setOnClickListener {
val item = data[adapterPosition]
listener(item)
}
}
fun bind(model: Model) = Unit // ...
}
}
Code language: Kotlin (kotlin)
This is a very basic implementation of clickListener
in RecyclerView
. There are a number of issues within this code:
-
Holder
invokeslistener
and obtainsModel
fromdata
field ofAdapter
– breaks Single Responsibility and Separation of Concerns principles, Adapter
cannot rely ondata
field consistency, because it’s modifiable byHolder
– breaks Encapsulation ofAdapter
,Holder
relies on use of specificAdapter
– Tight coupling thus no Reusability is possible.
In the case of RecyclerView
‘s Adapter
, this is often tolerated. But from my experience, once you learn something bad on a small scale, you will tend to use it on a large scale as well.
Functional composition to the rescue!
Let’s adjust the code with the functional composition:
class Adapter(private val clickListener: (Model) -> Unit) : RecyclerView.Adapter<Adapter.Holder>() {
private val data: MutableList<Model> = mutableListOf()
private val dataProvider: (Int) -> Model = { position ->
data[position]
}
private val clickComposite: (Int) -> Unit = { position ->
clickListener(dataProvider(position))
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder =
Holder(ModelView(parent.context), clickComposite)
override fun getItemCount(): Int = data.size
override fun onBindViewHolder(holder: Holder, position: Int): Unit =
holder.bind(data[position])
class Holder(view: View, positionProvider: (Int) -> Unit) : RecyclerView.ViewHolder(view) {
init {
view.setOnClickListener {
positionProvider(adapterPosition)
}
}
fun bind(model: Model) = Unit // ...
}
}
Code language: Kotlin (kotlin)
According to definition h(x) = g(f(x))
we can rename our functions to clickComposite(position) = clickListener(dataProvider(position))
. The composition consists of calling clickListener
with the result of dataProvider
applied on the provided position
.
As a result, we’ve mitigated all the mentioned issues:
Holder
is responsible only for propagating position,Adapter
for provide data on that position – Single Responsibility is satisfied,- the Separation of Concerns is achieved by moving position to Model mapping out of
Holder
, - by removing the
inner
keyword we’ve achieved Encapsulation ofAdapter
‘sdata
field and also decoupling both classes, allowing reusability ofHolder
.
Higher-order function
If you are an attentive reader you might have noticed, I’ve mixed two terms. Functional composition and function composition.
By the definition h(x) = g(f(x))
we have made a reactive code that was able to call function g
with the result of calling f(x)
. But we didn’t have the real h(x)
that can do composition, we had only lambda with that concrete composition, which is still error prone, boilerplate.
To address this, we can advance the previous solution to use a Higher-order function. For that purpose, we need some compose operator. So let’s just rewrite the mathematical definition into Kotlin:
/**
* Creates function composition that consists from composing [f] and [g] functions.
*
* @param f function accepting [X] and returning [Y]
* @param g function accepting [Y] and returning [Z]
*
* @return composed function accepting [X] and returning [Z].
*/
fun <X, Y, Z> compose(f: (X) -> Y, g: (Y) -> Z): (X) -> Z = { x -> g(f(x)) }
// Syntax sugar for `g after f` notation.
infix fun <X, Y, Z> ((Y) -> Z).after(f: (X) -> Y): (X) -> Z = compose(f, this)
Code language: Kotlin (kotlin)
And taste it:
class Adapter(private val clickListener: (Model) -> Unit) : RecyclerView.Adapter<Adapter.Holder>() {
private val data: MutableList<Model> = mutableListOf()
// Store to avoid recreation.
private val compositeFunction = clickListener after data::get
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder =
Holder(ModelView(parent.context), compositeFunction)
override fun getItemCount(): Int = data.size
override fun onBindViewHolder(holder: Holder, position: Int): Unit =
holder.bind(data[position])
class Holder(view: View, positionProvider: (Int) -> Unit) : RecyclerView.ViewHolder(view) {
init {
view.setOnClickListener {
positionProvider(adapterPosition)
}
}
fun bind(model: Model) = Unit // ...
}
}
Code language: Kotlin (kotlin)
As a result, we have the same benefits from the previous solution, but less boilerplate code. We are able to write Unit tests for the compose
function and ensure this code is working correctly. And the readability is also improved (unless you hate math :)).
Conclusion
Comparing the last two examples you might have noticed that the first example defines how the object (Holder
) will treat the data, while the following two defines what is object’s (Holder
) behaviour – what is his FUNCTION. This is the tiny difference that makes functional programming important.
You should also understand the difference between Functional Composition and Function Composition, how they are achieved, and what they can give you.
Which one should you choose? Well, it’s up to you. In the end, it’s only about what suits you (and your team) the best.