Search

Functional composition

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 f and g and produces a function h such that h(x) = g(f(x)). In this operation, the function g is applied to the result of applying the function f to x.

From Wikipedia: https://en.wikipedia.org/wiki/Function_composition

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:

  1.  Holder invokes listener and obtains Model from data field of Adapter – breaks Single Responsibility and Separation of Concerns principles,
  2. Adapter cannot rely on data field consistency, because it’s modifiable by Holder – breaks Encapsulation of Adapter,
  3. Holder relies on use of specific AdapterTight 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.

Note: Unfortunately, even the presence of lambda doesn’t make this a functional code! It just anonymizes the instance of the listener, which gives a bit more flexibility and independence, but nothing functional.

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:

  1. Holder is responsible only for propagating position, Adapter for provide data on that position – Single Responsibility is satisfied,
  2. the Separation of Concerns is achieved by moving position to Model mapping out of Holder,
  3. by removing the inner keyword we’ve achieved Encapsulation of Adapter‘s data field and also decoupling both classes, allowing reusability of Holder.

Note: Don’t be fooled with lambdas. This code uses functional principles, but it’s still fully convertible to interfaces. You don’t need lambdas for this. It will be just much uglier.

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.


PS: During the publishing process of this article, we found some blur in the understanding of what lambda and functional is.
I would like to dispel some myths. Lambda != functional. Functional code does not require you to use lambdas. You can write functional code with interfaces. You can also write non functional code with lambdas (that was the first example).
Lambdas describe behaviour, while interfaces define also the identity. You can understand lambdas as method polymorphism – they are matching based on function declaration. They are not meant to be used for modeling complexity, but rather to simplify complexity of a simple thing that you would normally achieve by polymorphism.