Lambdas and higher-order functions

Kotlin · Practical · 12 min

Lambdas power Kotlin's collection API, coroutines, DSLs and almost every modern Kotlin library.

A higher-order function takes or returns functions. Function types look like (Int, Int) -> Int or () -> Unit; nullable ones are ((String) -> Unit)?.

A lambda is written in braces: { a: Int, b: Int -> a + b }. Conventions that make Kotlin code read so smoothly:

  • If a lambda has exactly one parameter, you can omit it and use it: list.filter { it > 0 }.
  • Trailing lambdas: if the last parameter is a function, pass the lambda outside the parentheses: repeat(3) { println(it) }. If it's the only argument, drop the parentheses entirely.
  • The last expression in a lambda is its return value; return inside a lambda returns from the enclosing function only when the lambda is inlined, so use a label (return@forEach) to return from the lambda itself.
  • Unused parameters are written _.

Function references pass existing functions: ::isEven, String::length, user::greet, ::User (a constructor).

Lambdas can capture and even modify local vars from the enclosing scope (unlike Java, which requires effectively final variables).

Anonymous functions (fun(x: Int): Int { return x * 2 }) allow normal return semantics when you need them.

Kotlin's standard library is built on these: map, filter, sortedBy, let, apply, use, lazy, thread { }.

Example

fun applyTwice(f: (Int) -> Int, x: Int): Int = f(f(x))

fun isEven(n: Int) = n % 2 == 0

fun makeCounter(): () -> Int {
    var count = 0
    return { ++count }                  // captures and mutates count
}
fun main() {
    val add: (Int, Int) -> Int = { a, b -> a + b }
    println(add(2, 3))                  // 5
    println(applyTwice({ it * 3 }, 2))  // 18
    val nums = listOf(1, 2, 3, 4)
    println(nums.filter(::isEven))      // [2, 4]
    println(listOf("a", "bcd").map(String::length)) // [1, 3]
    repeat(2) { i -> print("r$i ") }    // r0 r1
    println()
    val next = makeCounter()
    next(); next()
    println(next())                     // 3
    nums.forEach {
        if (it == 2) return@forEach     // skip, like continue
        print(it)                       // 134
    }
}

Remember

  • Function types: (A, B) -> R; lambdas: { a, b -> ... }
  • it names a single parameter; trailing lambdas go outside parentheses
  • ::name creates function references
  • Use return@label to return from a lambda

Watch out for

  • Writing return inside a forEach lambda and unintentionally returning from the whole function.

Check yourself

  1. In list.forEach { if (it == 2) return }, inside fun main(), what does return do?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: kotlinlang.org/docs/lambdas.html