Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Higher-Order Functions and Lambda Expressions in Kotlin

Tech 1

Higher-order functions accept other functions as parameters or return them. A classic example is a custom aggregation function on a collection, which takes an initial value and a combining operation to process each element sequentially.

kotlin fun <T, U> Iterable<T>.aggregate( seed: U, operation: (U, T) -> U ): U { var state = seed for (item in this) { state = operation(state, item) } return state }

The operation parameter has a function type (U, T) -> U. Calling this higher-order function requires passing a function instance, where lambda expressions are typically utilized.

kotlin val numbers = listOf(10, 20, 30)

val total = numbers.aggregate(0) { acc, num -> acc + num }

val concatenated = numbers.aggregate("Items:") { acc, num -> "$acc $num" }

val product = numbers.aggregate(1, Int::times)

Function Types

Kotlin defines function types using a notation like (Int, String) -> Boolean. The syntax consists of parameter types in parentheses and a return type. () -> Unit represents a function with no parameters and no meaningful return value.

A function type can include a receiver type, written as A.(B) -> C. This indicates the function is called on a receiver object of type A with an argument of type B, returning type C. Suspended functions use the suspend modifier, such as suspend () -> Unit.

Parameter names can be included for clarity: (x: Int, y: Int) -> Coordinate. Nullable function types require parentheses: ((Int, Int) -> Int)?. Parentheses also control associativity: (Int) -> ((Int) -> Unit) differs from ((Int) -> Int) -> Unit.

Type aliases simplify complex function signatures:

kotlin typealias ClickListener = (View, ClickEvent) -> Unit

Instantiating Function Types

Function type instances can be obtained in several ways:

  1. Function literals:
    • Lambda expressions: { a, b -> a * b }
    • Anonymous functions: fun(s: String): Int { return s.toIntOrNull() ?: 0 }
  2. Callable references:
    • Functions: ::isOdd, String::toInt
    • Properties: List<Int>::size
    • Constructors: ::Regex
    • Bound references: item::toString
  3. Custom classes implementing the function interface:

kotlin class StringParser : (String) -> Int { override fun invoke(input: String): Int = input.toIntOrNull() ?: -1 } val parser: (String) -> Int = StringParser()

The compiler infers function types when possible: val doubler = { i: Int -> i * 2 } infers (Int) -> Int.

Non-literal values of function types with and without receivers are interchangeable. A value of (A, B) -> C can be assigned where A.(B) -> C is expected, and vice versa.

kotlin val repeatOp: String.(Int) -> String = { times -> this.repeat(times) } val standardOp: (String, Int) -> String = repeatOp

fun execute(f: (String, Int) -> String): String = f("hello", 3)

val output = execute(repeatOp)

Invoking Function Type Instances

Function type instances are invoked via the invoke operator or directly by name: f.invoke(x) or f(x). If the instance has a receiver type, the receiver object becomes the first argument, or it can be called as an extension:

kotlin val concat: (String, String) -> String = String::plus val subtract: Int.(Int) -> Int = Int::minus

println(concat.invoke("A", "B")) println(subtract(10, 5)) println(10.subtract(3))

Lambda Expressions and Anonymous Functions

Lambda expressions and anonymous functions are function literals—undeclared functions passed immediately as expressions.

Lambda Syntax

Full syntax encloses parameters and an arrow before the body: { x: Int, y: Int -> x + y }. If the return type is not Unit, the last expression is returned implicitly.

Trailing Lambdas

If a function's last parameter is a function, the lambda can be placed outside the parentheses. If it's the only parameter, parentheses can be omitted entirely.

kotlin val product = numbers.aggregate(1) { acc, num -> acc * num } run { println("Executing") }

Implicit Single Parameter: it

When a lambda has exactly one parameter and its type can be inferred, the parameter declaration and -> can be omitted. The parameter is accessible via it.

kotlin nummbers.filter { it > 0 }

Returning Values

Lambdas return the value of their last expression. Explicit returns use a qualified label:

kotlin numbers.filter { val isValid = it > 0 return@filter isValid }

Underscore for Unused Variables

Unused lambda parameters can be replaced with an underscore:

kotlin map.forEach { (_, value) -> println(value) }

Anonymous Functions

Anonymous functions allow explicit return type declarations where inference is insufficient. They resemble standard functions but lack a name.

kotlin fun(x: Int, y: Int): Int = x + y

Unlike lambdas, anonymous functions do not support trailing syntax and must be placed inside parentheses. Furthermore, a return statement inside an anonymous function returns from the anonymous function itself, whereas a return inside a lambda returns from the enclosing function.

Closures

Lambdas, anonymous functions, and local functions can access and modify variables declared in their outer scope (closures).

Function Literals with Receivers

Function literals can specify a receiver type, similar to extension functions. Inside the literal body, the receiver object is available via this, and its members can be accessed directly.

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

Anonymous functions explicitly declare the receiver type before the parameters:

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

When the receiver type is inferred from context, lambdas can serve as function literals with receivers. This is fundamental for building type-safe DSLs:

kotlin class Document { fun header() { /* ... */ } }

fun buildDocument(setup: Document.() -> Unit): Document { val doc = Document() doc.setup() return doc }

buildDocument { header() }

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.