Higher-Order Functions and Lambdas

Kotlin functions are first-class, which means that they can be stored in variables and data structures, passed as arguments to and returned from other higher-order functions. You can operate with functions in any way that is possible for other non-function values.

To facilitate this, Kotlin, as a statically typed programming language, uses a family of function types to represent functions and provides a set of specialized language constructs, such as lambda expressions.

Higher-Order Functions

A higher-order function is a function that takes functions as parameters, or returns a function.

A good example is the functional programming idiom fold) for collections, which takes an initial accumulator value and a combining function and builds its return value by consecutively combining current accumulator value with each collection element, replacing the accumulator:

  1. fun <T, R> Collection<T>.fold(
  2. initial: R,
  3. combine: (acc: R, nextElement: T) -> R
  4. ): R {
  5. var accumulator: R = initial
  6. for (element: T in this) {
  7. accumulator = combine(accumulator, element)
  8. }
  9. return accumulator
  10. }

In the code above, the parameter combine has a function type (R, T) -> R, so it accepts a function that takes two arguments of types R and T and returns a value of type R. It is invoked inside the for-loop, and the return value is then assigned to accumulator.

To call fold, we need to pass it an instance of the function type as an argument, and lambda expressions (described in more detail below) are widely used for this purpose at higher-order function call sites:

  1. fun main() {
  2. //sampleStart
  3. val items = listOf(1, 2, 3, 4, 5)
  4. // Lambdas are code blocks enclosed in curly braces.
  5. items.fold(0, {
  6. // When a lambda has parameters, they go first, followed by '->'
  7. acc: Int, i: Int ->
  8. print("acc = $acc, i = $i, ")
  9. val result = acc + i
  10. println("result = $result")
  11. // The last expression in a lambda is considered the return value:
  12. result
  13. })
  14. // Parameter types in a lambda are optional if they can be inferred:
  15. val joinedToString = items.fold("Elements:", { acc, i -> acc + " " + i })
  16. // Function references can also be used for higher-order function calls:
  17. val product = items.fold(1, Int::times)
  18. //sampleEnd
  19. println("joinedToString = $joinedToString")
  20. println("product = $product")
  21. }

The following sections explain in more detail the concepts mentioned so far.

Function types

Kotlin uses a family of function types like (Int) -> String for declarations that deal with functions: val onClick: () -> Unit = ....

These types have a special notation that corresponds to the signatures of the functions, i.e. their parameters and return values:

  • All function types have a parenthesized parameter types list and a return type: (A, B) -> C denotes a type that represents functions taking two arguments of types A and B and returning a value of type C. The parameter types list may be empty, as in () -> A. The Unit return type cannot be omitted.

  • Function types can optionally have an additional receiver type, which is specified before a dot in the notation: the type A.(B) -> C represents functions that can be called on a receiver object of A with a parameter of B and return a value of C. Function literals with receiver are often used along with these types.

  • Suspending functions belong to function types of a special kind, which have a suspend modifier in the notation, such as suspend () -> Unit or suspend A.(B) -> C.

The function type notation can optionally include names for the function parameters: (x: Int, y: Int) -> Point. These names can be used for documenting the meaning of the parameters.

To specify that a function type is nullable, use parentheses: ((Int, Int) -> Int)?.

Function types can be combined using parentheses: (Int) -> ((Int) -> Unit)

The arrow notation is right-associative, (Int) -> (Int) -> Unit is equivalent to the previous example, but not to ((Int) -> (Int)) -> Unit.

You can also give a function type an alternative name by using a type alias:

  1. typealias ClickHandler = (Button, ClickEvent) -> Unit

Instantiating a function type

There are several ways to obtain an instance of a function type:

  • Using a code block within a function literal, in one of the forms:

    Function literals with receiver can be used as values of function types with receiver.

  • Using a callable reference to an existing declaration:

    • a top-level, local, member, or extension function: ::isOdd, String::toInt,
    • a top-level, member, or extension property: List<Int>::size,
    • a constructor: ::Regex

    These include bound callable references that point to a member of a particular instance: foo::toString.

  • Using instances of a custom class that implements a function type as an interface:

  1. class IntTransformer: (Int) -> Int {
  2. override operator fun invoke(x: Int): Int = TODO()
  3. }
  4. val intFunction: (Int) -> Int = IntTransformer()

The compiler can infer the function types for variables if there is enough information:

  1. val a = { i: Int -> i + 1 } // The inferred type is (Int) -> Int

Non-literal values of function types with and without receiver are interchangeable, so that the receiver can stand in for the first parameter, and vice versa. For instance, a value of type (A, B) -> C can be passed or assigned where a A.(B) -> C is expected and the other way around:

  1. fun main() {
  2. //sampleStart
  3. val repeatFun: String.(Int) -> String = { times -> this.repeat(times) }
  4. val twoParameters: (String, Int) -> String = repeatFun // OK
  5. fun runTransformation(f: (String, Int) -> String): String {
  6. return f("hello", 3)
  7. }
  8. val result = runTransformation(repeatFun) // OK
  9. //sampleEnd
  10. println("result = $result")
  11. }

Note that a function type with no receiver is inferred by default, even if a variable is initialized with a reference to an extension function. To alter that, specify the variable type explicitly.

Invoking a function type instance

A value of a function type can be invoked by using its invoke(...) operator: f.invoke(x) or just f(x).

If the value has a receiver type, the receiver object should be passed as the first argument. Another way to invoke a value of a function type with receiver is to prepend it with the receiver object, as if the value were an extension function: 1.foo(2),

Example:

  1. fun main() {
  2. //sampleStart
  3. val stringPlus: (String, String) -> String = String::plus
  4. val intPlus: Int.(Int) -> Int = Int::plus
  5. println(stringPlus.invoke("<-", "->"))
  6. println(stringPlus("Hello, ", "world!"))
  7. println(intPlus.invoke(1, 1))
  8. println(intPlus(1, 2))
  9. println(2.intPlus(3)) // extension-like call
  10. //sampleEnd
  11. }

Inline functions

Sometimes it is beneficial to use inline functions, which provide flexible control flow, for higher-order functions.

Lambda Expressions and Anonymous Functions

Lambda expressions and anonymous functions are ‘function literals’, i.e. functions that are not declared, but passed immediately as an expression. Consider the following example:

  1. max(strings, { a, b -> a.length < b.length })

Function max is a higher-order function, it takes a function value as the second argument. This second argument is an expression that is itself a function, i.e. a function literal, which is equivalent to the following named function:

  1. fun compare(a: String, b: String): Boolean = a.length < b.length

Lambda expression syntax

The full syntactic form of lambda expressions is as follows:

  1. val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }

A lambda expression is always surrounded by curly braces, parameter declarations in the full syntactic form go inside curly braces and have optional type annotations, the body goes after an -> sign. If the inferred return type of the lambda is not Unit, the last (or possibly single) expression inside the lambda body is treated as the return value.

If we leave all the optional annotations out, what’s left looks like this:

  1. val sum = { x: Int, y: Int -> x + y }

Passing trailing lambdas

In Kotlin, there is a convention: if the last parameter of a function is a function, then a lambda expression passed as the corresponding argument can be placed outside the parentheses:

  1. val product = items.fold(1) { acc, e -> acc * e }

Such syntax is also known as trailing lambda.

If the lambda is the only argument to that call, the parentheses can be omitted entirely:

  1. run { println("...") }

it: implicit name of a single parameter

It’s very common that a lambda expression has only one parameter.

If the compiler can figure the signature out itself, it is allowed not to declare the only parameter and omit ->. The parameter will be implicitly declared under the name it:

  1. ints.filter { it > 0 } // this literal is of type '(it: Int) -> Boolean'

Returning a value from a lambda expression

We can explicitly return a value from the lambda using the qualified return syntax. Otherwise, the value of the last expression is implicitly returned.

Therefore, the two following snippets are equivalent:

  1. ints.filter {
  2. val shouldFilter = it > 0
  3. shouldFilter
  4. }
  5. ints.filter {
  6. val shouldFilter = it > 0
  7. return@filter shouldFilter
  8. }

This convention, along with passing a lambda expression outside parentheses, allows for LINQ-style) code:

  1. strings.filter { it.length == 5 }.sortedBy { it }.map { it.toUpperCase() }

Underscore for unused variables (since 1.1)

If the lambda parameter is unused, you can place an underscore instead of its name:

  1. map.forEach { _, value -> println("$value!") }

Destructuring in lambdas (since 1.1)

Destructuring in lambdas is described as a part of destructuring declarations.

Anonymous functions

One thing missing from the lambda expression syntax presented above is the ability to specify the return type of the function. In most cases, this is unnecessary because the return type can be inferred automatically. However, if you do need to specify it explicitly, you can use an alternative syntax: an anonymous function.

  1. fun(x: Int, y: Int): Int = x + y

An anonymous function looks very much like a regular function declaration, except that its name is omitted. Its body can be either an expression (as shown above) or a block:

  1. fun(x: Int, y: Int): Int {
  2. return x + y
  3. }

The parameters and the return type are specified in the same way as for regular functions, except that the parameter types can be omitted if they can be inferred from context:

  1. ints.filter(fun(item) = item > 0)

The return type inference for anonymous functions works just like for normal functions: the return type is inferred automatically for anonymous functions with an expression body and has to be specified explicitly (or is assumed to be Unit) for anonymous functions with a block body.

Note that anonymous function parameters are always passed inside the parentheses. The shorthand syntax allowing to leave the function outside the parentheses works only for lambda expressions.

One other difference between lambda expressions and anonymous functions is the behavior of non-local returns. A return statement without a label always returns from the function declared with the fun keyword. This means that a return inside a lambda expression will return from the enclosing function, whereas a return inside an anonymous function will return from the anonymous function itself.

Closures

A lambda expression or anonymous function (as well as a local function and an object expression) can access its closure, i.e. the variables declared in the outer scope. The variables captured in the closure can be modified in the lambda:

  1. var sum = 0
  2. ints.filter { it > 0 }.forEach {
  3. sum += it
  4. }
  5. print(sum)

Function literals with receiver

Function types with receiver, such as A.(B) -> C, can be instantiated with a special form of function literals – function literals with receiver.

As said above, Kotlin provides the ability to call an instance of a function type with receiver providing the receiver object.

Inside the body of the function literal, the receiver object passed to a call becomes an implicit this, so that you can access the members of that receiver object without any additional qualifiers, or access the receiver object using a this expression.

This behavior is similar to extension functions, which also allow you to access the members of the receiver object inside the body of the function.

Here is an example of a function literal with receiver along with its type, where plus is called on the receiver object:

  1. val sum: Int.(Int) -> Int = { other -> plus(other) }

The anonymous function syntax allows you to specify the receiver type of a function literal directly. This can be useful if you need to declare a variable of a function type with receiver, and to use it later.

  1. val sum = fun Int.(other: Int): Int = this + other

Lambda expressions can be used as function literals with receiver when the receiver type can be inferred from context. One of the most important examples of their usage is type-safe builders:

  1. class HTML {
  2. fun body() { ... }
  3. }
  4. fun html(init: HTML.() -> Unit): HTML {
  5. val html = HTML() // create the receiver object
  6. html.init() // pass the receiver object to the lambda
  7. return html
  8. }
  9. html { // lambda with receiver begins here
  10. body() // calling a method on the receiver object
  11. }