Lambdas and higher-order functions.
Functions are powerful. But so far, we have defined them and called them. What if we could pass them around like values? Store them in variables? This is functional programming. Today we learn lambdas and higher-order functions.
In Kotlin, functions can be treated as values.
You can store a function in a variable. You can pass a function to another function. You can return a function from a function.
This might seem abstract. Why would you do this?
Consider: you want to filter a list. The filtering logic varies. Sometimes you want even numbers. Sometimes you want numbers greater than 10. The pattern is the same. The specific check differs.
You could write separate filter functions for each case. Or you could write one filter that takes the check as a parameter. The check is a function.
Functions as values enable this flexibility.
A lambda is an anonymous function. A function without a name.
The syntax: braces containing parameters, arrow, body.
Open brace, x colon Int, arrow, x times 2, close brace. This is a lambda that takes an Int and returns it doubled.
Open brace, a colon Int, b colon Int, arrow, a plus b, close brace. Two parameters, returns their sum.
Open brace, arrow, println Hello, close brace. No parameters.
If there is one parameter, you can omit the declaration and use it: open brace, it times 2, close brace. The implicit name is it.
Lambdas are expressions. They evaluate to a function value that you can use.
Every function has a type.
A function that takes an Int and returns a String has type parenthesis Int close parenthesis arrow String.
A function that takes two Ints and returns an Int has type parenthesis Int comma Int close parenthesis arrow Int.
A function that takes nothing and returns Unit has type parenthesis close parenthesis arrow Unit.
You can declare variables with function types: val double colon parenthesis Int close parenthesis arrow Int equals open brace it times 2 close brace.
The type tells you what the function expects and what it produces. Just like other types tell you about values.
Function types make functions first-class citizens. They can go anywhere a type goes.
A higher-order function is a function that takes functions as parameters or returns functions.
fun applyTwice with x Int and operation of type Int to Int returning Int, return operation of operation of x.
applyTwice takes a number and a function. It applies the function twice.
val result equals applyTwice 3 with lambda it times 2. Start with 3, double to 6, double to 12.
Higher-order functions abstract over actions. filter takes a predicate function. map takes a transformation function. forEach takes an action function.
You provide the specific behavior. The higher-order function provides the structure.
When the last parameter is a function, you can move the lambda outside the parentheses.
listOf 1, 2, 3 dot filter parenthesis open brace it greater than 1 close brace close parenthesis becomes listOf 1, 2, 3 dot filter open brace it greater than 1 close brace.
If the lambda is the only parameter, you can omit the parentheses entirely: run open brace println Hello close brace.
This makes code read more naturally. The lambda looks like a code block following the function.
Trailing lambda is syntactic sugar. The meaning is the same. The readability improves.
When a lambda has exactly one parameter, you do not need to declare it.
Open brace x arrow x times 2 close brace can be written as open brace it times 2 close brace.
it is an implicit name for the single parameter.
This reduces noise for simple lambdas. Most collection operations use single-parameter lambdas: filter open brace it greater than 0 close brace, map open brace it dot uppercase close brace.
If you need a more descriptive name, declare it explicitly: open brace number arrow number times 2 close brace. Use it for short, clear lambdas.
Some lambdas have a receiver, like extension functions.
The type looks like Int dot parenthesis close parenthesis arrow String. A lambda with Int as receiver, returning String.
Inside such a lambda, this refers to the receiver.
This enables DSLs and builder patterns. apply, run, and with use lambdas with receivers.
val result equals buildString open brace append Hello semicolon append space semicolon append World close brace. Inside the lambda, this is the StringBuilder.
Lambdas with receivers make configuration code clean and readable.
return in a lambda behaves differently than in a function.
A plain return in a lambda returns from the enclosing function, not just the lambda.
To return from just the lambda, use a labeled return: return at forEach or return at filter.
Alternatively, the last expression in a lambda is its return value. No return keyword needed.
Open brace x arrow x times 2 close brace. The result of x times 2 is returned.
Open brace x arrow if x greater than 0 then x else 0 close brace. The last expression is the result.
Let us summarize.
Functions can be values. Store them, pass them, return them.
Lambdas are anonymous functions: open brace parameters arrow body close brace.
Function types describe what functions accept and return: parenthesis Int close parenthesis arrow String.
Higher-order functions take or return functions.
Trailing lambda syntax moves the lambda outside parentheses.
it is the implicit name for single parameters.
Lambdas enable flexible, reusable code patterns.