By Dmitry Jemerov and Svetlana Isakova

Save 37% off Kotlin in Action with code fccjemerov.

The key idea of this article is the concept of higher-order functions. A higher-order function is a function that takes another function as an argument or returns one. In Kotlin, functions can be represented as values using lambdas or function references. Therefore, a higher-order function is any function to which you can pass a lambda or a function reference as an argument, or a function which returns one, or both. For example, the filter standard-library function takes a predicate function as an argument and is therefore a higher-order function:

 
  
list.filter { x > 0 }
  

Many higher-order functions are declared in the Kotlin standard library: mapwith, and others. Now you’ll learn how to declare such functions in your own code. To do this, you must first be introduced to function types.


Function types

To declare a function that takes a lambda as an argument, you need to know how to declare the type of the corresponding parameter. Before we get to this, let’s look at a simpler case and store a lambda in a local variable. You already saw how you can do this without declaring the type, relying on Kotlin’s type inference:

 
  
val sum = { x: Int, y: Int -> x + y } 
val action = { println(42) }
  

In this case, the compiler infers that both the sum and action variables have function types. Now let’s see what an explicit type declaration for these variables looks like:

 
  
val sum: (Int, Int) -> Int = { x, y -> x + y }     ❶  
val action: () -> Unit = { println(42) }           ❷  
  

  Function that takes two Int parameters and returns an Int value

  Function that takes no arguments and doesn’t return a value

To declare a function type, you put the function parameter types in parentheses, followed by an arrow and the return type of the function (see figure 1).


Figure 1. Function-type syntax in Kotlin


As you remember, the Unit type is used to specify that a function returns no meaningful value. The Unit return type can be omitted when you declare a regular function, but a function type declaration always requires an explicit return type, and you can’t omit Unit in this context.

Note how you can omit the types of the parameters x, y in the lambda expression { x, y \-> x + y }. Because they’re specified in the function type as part of the variable declaration, you don’t need to repeat them in the lambda itself.

As with any other function, the return type of a function type can be marked as nullable:

 
  
var canReturnNull: (Int, Int) -> Int? = { null }
  

You can also define a nullable variable of a function type. To specify that the variable itself, rather than the return type of the function, is nullable, you need to enclose the entire function type definition in parentheses and put the question mark after the parentheses:

 
  
var funOrNull: ((Int, Int) -> Int)? = null
  

Note the subtle difference between this example and the previous one. If you omit the parentheses, you’ll declare a function type with a nullable return type, and not a nullable variable of a function type.

Parameter names of function types

You can specify names for parameters of a function type:

 
  
fun performRequest( 
       url: String, 
       callback: (code: Int, content: String) -> Unit     
) { 
    /*...*/ 
} 
  
>>> val url = "http://kotl.in" 
>>> performRequest(url) { code, content -> /*...*/ }      
>>> performRequest(url) { code, page -> /*...*/ }        
  

   The function type now has named parameters

  You can use the names provided in the API as lambda argument names …

 … or you can change them

Parameter names don’t affect type matching. When you declare a lambda, you don’t have to use the same parameter names as the ones used in the function type declaration. The names improve readability of the code and can be used in the IDE for code completion.

Calling functions passed as arguments

Now that you know how to declare a higher-order function, let’s discuss how to implement one. The first example is as simple as possible and uses the same type declaration as the sum lambda you saw earlier. The function performs an arbitrary operation on two numbers, 2 and 3, and prints the result.

Listing 1. Implementing a higher-order function

 
  
fun twoAndThree(operation: (Int, Int) -> Int) {   
    val result = operation(2, 3)                  
    println("The result is $result") 
} 
  
>>> twoAndThree { a, b -> a + b } 
The result is 5 
>>> twoAndThree { a, b -> a * b } 
The result is 6
  

 Declares a parameter of a function type

 Calls the parameter of a function type

The syntax for calling the function passed as an argument is the same as calling a regular function – you put the parentheses after the function name and you put the parameters inside the parentheses.

As a more interesting example, let’s reimplement one of the most commonly used standard library functions: the filter function. To keep things simple, you’ll implement the filter function on String, but the generic version that works on a collection of any elements is similar. Its declaration is shown in figure 2.


Figure 2. Declaration of the filter function, taking a predicate as a parameter


The filter function takes a predicate as a parameter. The type of predicate is a function that takes a character parameter and returns a boolean result. The result is true if the character passed to the predicate needs to be present in the resulting string, or false otherwise. Here’s how the function can be implemented:


Listing 2. Implementing the filter function

 
  
fun String.filter(predicate: (Char) -> Boolean): String { 
    val sb = StringBuilder() 
    for (index in 0 until length) { 
        val element = get(index) 
        if (predicate(element)) sb.append(element)    ❶  
    } 
    return sb.toString() 
} 
  
>>> println("ab1c".filter { it in 'a'..'z' })         ❷  
abc
  

  Calls the function passed as the argument for the “predicate” parameter

  Passes a lambda as an argument for “predicate”


The filter function implementation is straightforward. It checks whether each character satisfies the predicate and, on success, adds it to the StringBuilder containing the result.

IntelliJ IDEA tip

IntelliJ IDEA supports smart steps into lambda code in the debugger (for the cases when it’s possible—when lambdas are inlined). If you step through the previous example, you’ll see how execution moves between the body of the filter function and the lambda you pass through it, as the function processes each element in the input list.


Using function types from Java

Under the hood, function types are declared as regular interfaces; a variable of a function type is an implementation of a FunctionN interface. The Kotlin standard library defines a series of interfaces, corresponding to different numbers of function arguments: Function0<R> (this function takes no arguments), Function1<P1, R> (this function takes one argument), etc. Each interface defines a single invoke method, and calling it executes the function. A variable of a function type is an instance of a class implementing the corresponding FunctionN interface, with the invoke method containing the body of the lambda.

Kotlin functions that use function types can be called from Java. Java 8 lambdas are automatically converted to values of function types:

 
  
/* Kotlin declaration */ 
fun processTheAnswer(f: (Int) -> Int) { 
    println(f(42)) 
} 
/* Java */ 
>>> processTheAnswer(number -> number + 1); 
43
  

In older Java versions, you can pass an instance of an anonymous class implementing the invoke method from the corresponding function interface:

 
  
/* Java */ 
>>> processTheAnswer( 
...     new Function1<Integer, Integer>() {          ❶  
...         @Override 
...         public Integer invoke(Integer number) { 
...             System.out.println(number); 
...             return number + 1; 
...         } 
...     }); 
43
  

  Uses the Kotlin function type from Java code (prior to Java 8)

In Java, you can use extension functions from the Kotlin standard library that expect lambdas as arguments. Note that they don’t look as nice as in Kotlin—you must pass a receiver object as a first argument explicitly:

 
  
/* Java */ 
>>> List<String> strings = new ArrayList(); 
>>> strings.add("42"); 
>>> CollectionsKt.forEach(strings, s -> {    ❶  
...    System.out.println(s); 
...    return Unit.INSTANCE;                 ❷  
... });
  

  You can use a function from the Kotlin standard library in Java code

  You must return a value of Unit type explicitly

In Java, your function or lambda can return Unit, but because the Unit type has a value in Kotlin, you need to return it explicitly. You can’t pass a lambda returning void as an argument of a function type that returns Unit, like (String) -> Unit in the previous example.

Default and null values for parameters with function types

When you declare a parameter of a function type, you can also specify its default value. To see where this can be useful, let’s go back to an example of a joinToString function. Here’s the implementation.


Listing 3. Implementation of a joinToString function

 
  
fun <T> Collection<T>.joinToString( 
        separator: String = ", ", 
        prefix: String = "", 
        postfix: String = "" 
): String { 
    val result = StringBuilder(prefix) 
  
    for ((index, element) in this.withIndex()) { 
        if (index > 0) result.append(separator) 
        result.append(element)                     ❶  
    } 
  
    result.append(postfix) 
    return result.toString() 
}
  

  Converts the object to a string using the default toString method


This implementation is flexible, but it doesn’t let you control one key aspect of the conversion: how individual values in the collection are converted to strings. The code uses StringBuilder.append(o: Any?), which always converts the object to a string using the toString method. This is good in a lot of cases, but not always. You now know that you can pass a lambda to specify how values are converted into strings, but requiring all callers to pass that lambda would be cumbersome, because most of them are okay with the default behavior. To solve this, you can define a parameter of a function type and specify a default value for it as a lambda.


Listing 4. Defining the parameter and specifying the default value

 
  
fun <T> Collection<T>.joinToString( 
        separator: String = ", ", 
        prefix: String = "", 
        postfix: String = "", 
        transform: (T) -> String = { it.toString() }   ❶  
): String { 
    val result = StringBuilder(prefix) 
  
    for ((index, element) in this.withIndex()) { 
        if (index > 0) result.append(separator) 
        result.append(transform(element))               ❷  
    } 
  
    result.append(postfix) 
    return result.toString() 
} 
  
>>> val letters = listOf("Alpha", "Beta") 
>>> println(letters.joinToString())                     ❸  
Alpha, Beta 
>>> println(letters.joinToString { it.toLowerCase() })  ❹  
alpha, beta 
>>> println(letters.joinToString(separator = "! ", postfix = "! ", 
...        transform = { it.toUpperCase() }))           ❺  
ALPHA! BETA!
  

  Declares a parameter of a function type with a lambda as a default value

  Calls the function passed as an argument for the “transform” parameter

  Uses the default conversion function

  Passes a lambda as an argument

  Uses the named argument syntax for passing several arguments, including a lambda


Note that this function is generic: it has a type parameter T denoting the type of the element in a collection. The transform lambda receives an argument of that type.

Declaring a default value of a function type requires no special syntax—you put the value as a lambda after the = sign. The examples show different ways of calling the function: omitting the lambda entirely (the default toString() conversion is used); passing it outside of the parentheses; and passing it as a named argument.

An alternative approach is to declare a parameter of a nullable function type. Note that you can’t call the function passed in such a parameter directly – Kotlin will refuse to compile such code, because it detects the possibility of null pointer exceptions in this case. One option is to check for null explicitly:

 
  
fun foo(callback: (() -> Unit)?) { 
    // ... 
    if (callback != null) { 
        callback() 
    } 
}
  

A shorter version makes use of the fact that a function type is an implementation of an interface with an invoke method. As a regular method, invoke can be called through the safe-call syntax: callback?.invoke().

Here’s how you can use this technique to rewrite the joinToString function.


Listing 5. Rewriting the joinToString function

 
  
fun <T> Collection<T>.joinToString( 
        separator: String = ", ", 
        prefix: String = "", 
        postfix: String = "", 
        transform: ((T) -> String)? = null     ❶  
): String { 
    val result = StringBuilder(prefix) 
  
    for ((index, element) in this.withIndex()) { 
        if (index > 0) result.append(separator) 
        val str = transform?.invoke(element)   ❷  
            ?: element.toString()              ❸  
        result.append(str) 
    } 
  
    result.append(postfix) 
    return result.toString() 
}
  

  Declares a nullable parameter of a function type

  Uses the safe-call syntax to call the function

  Uses the Elvis operator to handle the case when a callback isn’t specified


Now you know how to write functions that take functions as arguments. Let’s look next at the other kind of higher-order functions: functions that return other functions.


Returning functions from functions

The requirement to return a function from another function doesn’t come up as often as passing functions to other functions, but it’s still useful. For instance, imagine a piece of logic in a program that can vary depending on the state of the program or other conditions—for example, calculating the cost of shipping depending on the selected shipping method. You can define a function that chooses the appropriate logic variant and returns it as another function. Here’s how this looks as code.


Listing 6. Returning a function from a function

 
  
enum class Delivery { STANDARD, EXPEDITED } 
  
class Order(val itemCount: Int) 
  
fun getShippingCostCalculator( 
        delivery: Delivery): (Order) -> Double {            ❶  
    if (delivery == Delivery.EXPEDITED) { 
        return { order -> 6 + 2.1 * order.itemCount }       ❷  
    } 
  
    return { order -> 1.2 * order.itemCount }               ❷  
} 
  
>>> val calculator =                                        ❸  
...     getShippingCostCalculator(Delivery.EXPEDITED) 
>>> println("Shipping costs ${calculator(Order  )}")      ❹  
Shipping costs 12.3
  

  Declares a function that returns a function

  Returns lambdas from the function

  Stores the returned function in a variable

  Invokes the returned function


To declare a function that returns another function, you specify a function type as its return type. In listing 6, getShippingCostCalculator returns a function that takes an Order and returns a Double. To return a function, you write a return expression followed by a lambda, a member reference, or another expression of a function type, such as a local variable.

Let’s see another example where returning functions from functions is useful. Suppose you’re working on a GUI contact-management application, and you need to determine which contacts should be displayed, based on the state of the UI. Let’s say the UI allows you to type a string and then shows only contacts with names starting with that string; it also lets you hide contacts that don’t have a phone number specified. You’ll use the ContactListFilters class to store the state of the options.


Listing 7. Using the ContactListFilters class to store the state of the options

 
  
class ContactListFilters { 
    var prefix: String = "" 
    var onlyWithPhoneNumber: Boolean = false 
}
  

When a user types D to see the contacts whose first or last name starts with D, the prefix value is updated. We’ve omitted the code that makes the necessary changes – a full UI application would be too much code for an article, so we show a simplified example.

To decouple the contact-list display logic from the filtering UI, you can define a function that creates a predicate used to filter the contact list. This predicate checks the prefix and checks that the phone number is present, if required.


Listing 8. Predicate checking if the prefix and phone number are present

 
  
  
data class Person( 
        val firstName: String, 
        val lastName: String, 
        val phoneNumber: String? 
) 
  
class ContactListFilters { 
    var prefix: String = "" 
    var onlyWithPhoneNumber: Boolean = false 
  
    fun getPredicate(): (Person) -> Boolean {       ❶  
        val startsWithPrefix = { p: Person -> 
            p.firstName.startsWith(prefix) || p.lastName.startsWith(prefix) 
        } 
        if (!onlyWithPhoneNumber) { 
            return startsWithPrefix                 ❷  
        } 
        return { startsWithPrefix(it) 
                    && it.phoneNumber != null }     ❸  
    } 
} 
  
>>> val contacts = listOf(Person("Dmitry", "Jemerov", "123-4567"), 
...                       Person("Svetlana", "Isakova", null)) 
>>> val contactListFilters = ContactListFilters() 
>>> with (contactListFilters) { 
>>>     prefix = "Dm" 
>>>     onlyWithPhoneNumber = true 
>>> } 
>>> println(contacts.filter( 
...     contactListFilters.getPredicate()))         ❹  
[Person(firstName=Dmitry, lastName=Jemerov, phoneNumber=123-4567)]
  

  Declares a function that returns a function

❷  Returns a variable of a function type

 Returns a lambda from this function

  Passes the function returned by getPredicate as an argument to “filter”


The getPredicate method returns a function value that you pass to the filter function as an argument. Kotlin function types allow you to do this for values of other types, such as strings.

Higher-order functions give you an extremely powerful tool for improving the structure of your code and removing duplication. Let’s see how lambdas can help extract repeated logic from your code.


Removing duplication through lambdas

Function types and lambda expressions together constitute a great tool to create reusable code. Many kinds of code duplication that previously could be avoided only through cumbersome constructions can now be eliminated by using succinct lambda expressions.

Let’s look at an example that analyzes visits to a website. The class SiteVisit stores the path of each visit, its duration, and the user’s OS. Various OSs are represented with an enum.


Listing 9. Example analyzing visits to a website using SiteVisit

 
  
data class SiteVisit( 
    val path: String, 
    val duration: Double, 
    val os: OS 
) 
  
enum class OS { WINDOWS, LINUX, MAC, IOS, ANDROID } 
  
val log = listOf( 
    SiteVisit("/", 34.0, OS.WINDOWS), 
    SiteVisit("/", 22.0, OS.MAC), 
    SiteVisit("/login", 12.0, OS.WINDOWS), 
    SiteVisit("/signup", 8.0, OS.IOS), 
    SiteVisit("/", 16.3, OS.ANDROID) 
)
  

Imagine that you need to display the average duration of visits from Windows machines. You can perform the task using the average function.


Listing 10. Using the average function to display average duration of visits

 
  
val averageWindowsDuration = log 
    .filter { it.os == OS.WINDOWS } 
    .map(SiteVisit::duration) 
    .average() 
  
>>> println(averageWindowsDuration) 
23.0
  

Now, suppose you need to calculate the same statistics for Mac users. To avoid duplication, you can extract the platform as a parameter.


Listing 11. Extracting the platform as a parameter

 
  
fun List<SiteVisit>.averageDurationFor(os: OS) = ❶  
        filter { it.os == os }.map(SiteVisit::duration).average() 
  
>>> println(log.averageDurationFor(OS.WINDOWS)) 
23.0 
>>> println(log.averageDurationFor(OS.MAC)) 
22.0
  

  Duplicated code extracted into the function


Note how making this function an extension improves readability. You can even declare this function as a local extension function if it makes sense only in the local context.

But it’s not powerful enough. Imagine that you’re interested in the average duration of visits from the mobile platforms (currently you recognize two of them: iOS and Android).


Listing 12. Average visits from mobile platforms

 
  
val averageMobileDuration = log 
    .filter { it.os in setOf(OS.IOS, OS.ANDROID) } 
    .map(SiteVisit::duration) 
    .average() 
  
>>> println(averageMobileDuration) 
12.15
  

Now, a simple parameter representing the platform doesn’t do the job. It’s also likely that you’ll want to query the log with more complex conditions, such as “What’s the average duration of visits to the signup page from iOS?” Lambdas can help here. You can use function types to extract the required condition into a parameter.


Listing 13. Extracting the required condition into a parameter with function types

 
  
  
fun List<SiteVisit>.averageDurationFor(predicate: (SiteVisit) -> Boolean) = 
        filter(predicate).map(SiteVisit::duration).average() 
  
>>> println(log.averageDurationFor { 
...     it.os in setOf(OS.ANDROID, OS.IOS) }) 
12.15 
>>> println(log.averageDurationFor { 
...     it.os == OS.IOS && it.path == "/signup" }) 
8.0
  
  

Function types can help eliminate code duplication. If you’re tempted to copy and paste a piece of the code, it’s likely that the duplication can be avoided. With lambdas, you can extract not only the repeated data, but the behavior as well.

Note

Some well-known design patterns can be simplified using function types and lambda expressions. Let’s consider the Strategy pattern, for example. Without lambda expressions, it requires you to declare an interface with several implementations for each possible strategy. With function types in your language, you can use a general function type to describe the strategy, and pass different lambda expressions as different strategies.

If you’re interested in learning more about the inner workings of Kotlin, download the free first chapter of Kotlin in Action and see this Slideshare presentation. Use code fccjemerov to save 37% off Kotlin in Action (all formats) at manning.com.