Composing Higher-Order Functions in Kotlin

Photo by Michael Dziedzic on Unsplash

Higher-order functions are a feature of functional programming paradigm. As more and more languages are adopting functional paradigm, Kotlin is no behind.

According to Wikipedia,

In mathematics and computer science, a higher-order function is a function that does at least one of the following:

  • takes one or more functions as arguments
  • returns a function as its result.

So we can not only pass primitive and object types as arguments but also “functions” as arguments

Composing

Composing higher-order functions or using them can be confusing in the beginning. In this article, I intend to fix that.

Kotlin Standard Library is full of higher-order functions, from Scope Functions to Collections to Coroutines, they’re everywhere.

One such function you’ll find yourself using regularly with Collections is filter().

filter() source code looks like the following:


/**
 * Returns a list containing only elements matching the given [predicate].
 */
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}

Let’s say we are using filter() on a List<String>, then the function can be visualised as follows:

public inline fun <String> Iterable<String>.filter(predicate: (String) -> Boolean): List<String> {
    return filterTo(ArrayList<String>(), predicate)
}

predicate: (String) -> Boolean is the function we’re passing to a higher-order function filter(). It can be visualised as a regular function in the following way:

fun filterPredicate(arg: String): Boolean { /* return result */ }

predicate: (String) -> Boolean consists of 3 parts:

  • predicate -> denotes the name of the argument passed to filter() . There is nothing special about it and is similar to what we do in regular functions. We could name it whatever we like.
  • (String) -> denotes the arguments required to the function we’ve passed to the higher-order function. If no arguments required then it will be empty ().
  • Boolean -> denotes the return value of our passed function.

Another variation to filter() is filterIndexed() and it’s List<String> visualisation is as follows:

/**
 * Returns a list containing only elements matching the given [predicate].
 * @param [predicate] function that takes the index of an element and the element itself
 * and returns the result of predicate evaluation on the element.
 */
public inline fun <String> Iterable<String>.filterIndexed(predicate: (index: Int, String) -> Boolean): List<String> {
    return filterIndexedTo(ArrayList<String>(), predicate)
}

If we see in predicate: (index: Int, String) -> Boolean instead of taking a single argument, passed function to filterIndexed() takes 2 arguments i.e. Int & String(naming these arguments are optional)

Passing a function

Functions in functional programming and Kotlin are considered as first-class citizens. It enables functions or function references to be passed around like any other value, also it can be associated with a variable i.e. val myFunction = /* function */

3 ways in which we can work with Higher-order functions:

  • Lambdas
  • Anonymous functions
  • Function references

Consider the following list for further explanation:

val fruits = listOf("Orange","Apple","Mango","Peach","Watermelon")

Lambdas

Lambdas are the most common and cleanest way of working with higher-order functions.

Applying filter() on fruits using lambdas:

fruits.filter( { fruit -> fruit.length == 6 } ) // return Orange

In Lambdas, the value of the last expression is returned by default if not explicitly specified. In the above case, fruit.length == 6 resolves to return a Boolean value.

Anonymous Functions

As the name suggests, anonymous functions are nameless functions. Syntax to declare anonymous functions is similar to declaring regular functions but without a name.

Modifying the above example to use anonymous functions:

fruits.filter( fun(fruit): Boolean { return fruit.length == 6 } ) //returns Orange

Notice the syntax of the highlighted code snippet. It is required to explicitly return from anonymous functions.

Function References

Let’s say we have a member function already declared in our class, which takes fruit as a string argument and returns if it is a citrus fruit.

fun isCitrus(fruit: String): Boolean { return fruit == "Orange" }

We can pass this function into a higher-order function using :: operator.

fruits.filter( ::isCitrus ) //returns Orange

One thing to make sure while using function references is that the signature of the function is correct.

filter() takes the function of the type (String) -> Boolean and our function isCitrus() is also of the type (String) -> Boolean

Returning a function

Similarly, All 3 methods can be used to return functions as well. The syntax remains the same except for the function from which we are returning.

So let’s say for example purpose, we want to filter fruits based on time of the day.

fun getFilterCriteria(): (String) -> Boolean {
        val timeOfDay = getTime() //Imagine this working
        return when(timeOfDay){
            isDay -> {
                //return fruit that keep doctor away
                
                { fruit:String ->  fruit == "Apple"}  // Lambdas
            }

            isAfternoon -> {
                //return something pulpy
                
                ::isCitrus // function references
            }

            isNight -> {
                //return gaint fruit
                
                fun(fruit:String):Boolean{return fruit=="Watermelon"}//Anonymous functions
            }
            else -> ::isCitrus
        }
    }

Now calling filter() on fruits:

fruits.filter(getFilterCriteria())

Notice getFilterCriteria() returns (String) -> Boolean which is basically function type and that’s how Kotlin allows returning functions.

Conclusion

Higher-order functions can bring great flexibility to our code, while also making code concise and easy to maintain.

A great exercise will be to go back to your code and see if you can change parts of it to harness the power of Higher-order functions.

Leave a reply:

Your email address will not be published.

Site Footer

Sliding Sidebar