Introduction to Functional Programming in Kotlin
Kotlin is a modern multi-paradigm language that seamlessly combines object-oriented and functional programming. Thanks to its concise syntax and powerful capabilities, Kotlin allows you to write clean, predictable, and easily testable code. Functional programming (FP) in Kotlin is not just a trendy fad, but a powerful tool for solving everyday tasks: from collection processing to building asynchronous call chains.
In this article, we will break down the key concepts of FP as applied to Kotlin: data immutability, functions as first-class values, lambda expressions, higher-order functions, and collection processing chains (map, filter, reduce). All examples will be accompanied by practical code that you can run immediately in IntelliJ IDEA or an online compiler.
Fundamentals of the Functional Approach in Kotlin
Immutability
The fundamental principle of FP is working with immutable data. In Kotlin, the keywords val (immutable reference) and immutable collections (listOf, mapOf, setOf) are used for this.
// Immutable listval numbers: List<Int> = listOf(1, 2, 3, 4, 5)
// Attempting to modify will cause a compilation error// numbers.add(6) // Error!
// Instead of mutation, create a new collectionval newNumbers = numbers + 6 // [1, 2, 3, 4, 5, 6]The advantages of immutability are obvious: the code becomes thread-safe, easier to debug, and side effects are minimized. If you need to change data, you always create a new copy with the necessary changes.
Functions as First-Class Values
In Kotlin, functions are full-fledged objects. They can be assigned to variables, passed to other functions, and returned from functions. The function type is written in parentheses with an arrow:
// Function as a valueval sum: (Int, Int) -> Int = { a, b -> a + b }
// Usageval result = sum(10, 20) // 30
// Function that takes another functionfun operate(a: Int, b: Int, operation: (Int, Int) -> Int): Int { return operation(a, b)}
println(operate(5, 3, sum)) // 8println(operate(5, 3, { x, y -> x * y })) // 15Note: if the lambda is the last argument, Kotlin allows you to move it outside the parentheses — this is the trailing lambda syntax, which makes the code very readable.
Lambda Expressions and Higher-Order Functions
Lambda Expressions
A lambda is an anonymous function enclosed in curly braces. In Kotlin, lambdas can capture variables from the outer context (closures).
// Simple lambdaval greet = { name: String -> "Hello, $name!" }println(greet("Anna")) // Hello, Anna!
// Lambda with multiple parametersval multiply: (Int, Int) -> Int = { a, b -> a * b }
// If there is one parameter, you can use itval double = { it: Int -> it * 2 }println(double(5)) // 10Higher-Order Functions
These are functions that take other functions as arguments or return them. The Kotlin standard library has many such functions: let, apply, run, with, and others. Let's look at a practical example:
data class User(val name: String, val age: Int)
fun processUsers(users: List<User>, filter: (User) -> Boolean, transform: (User) -> String): List<String> { return users.filter(filter).map(transform)}
val users = listOf( User("Ivan", 25), User("Maria", 17), User("Peter", 30))
// Filter adults and output their namesval adultNames = processUsers( users, filter = { it.age >= 18 }, transform = { "Adult: ${it.name}" })