Kotlin Lambda Expressions
Overview
This page looks at lambda expressions and closures, which deal with anonymous functions. They seem similar, but there are differences. We will look at them here.
Anonymous Function
First, let’s look at examples to understand what an anonymous function is.
When you use :: with a predefined function, you can create a function object directly without going through a sequence of converting it into a function object. In the example below, (1), (2), and (3) produce the same result.
Example: Literal representation of function objects
// (1)
fun succ(n: Int): Int = n + 1
map(list, ::succ)
// (2)
map(list, fun(n: Int): Int { return n + 1 })
// (3)
map(list, {n: Int -> n + 1})
The technique of directly creating a function object, as in (2) or (3), is called a function literal. It is also called an anonymous function.
Generalizing the format of a function literal like (3) gives the following.
{argument list -> function body}
Lambda Expression
A lambda expression is also simply called a lambda, and it means an anonymous function. An anonymous function is a function without a name or with the name omitted. Anonymous functions are usually created for functions that are used once and not reused, and they can be assigned to variables or specified as callback functions. This lets you create anonymous functions directly in code without defining separate functions, improving readability. It is a common pattern in functional programming.
fun main() {
var add1 = fun(a: Int, b: Int): Int = a + b
println(add1(3, 5))
}
Output:
8
Closure
Note that function literals require braces. Depending on the context, some notation can also be omitted.
Example: Function literal notation
// Type inference is possible inside a function literal.
val foo: (Int) -> Int = { n ->
n + 1
}
// If there is one argument, it can implicitly be referenced as it.
val bar: (Int) -> Int = {
it + 1
}
// Function literal with multiple statements
val baz: (Int) -> Int = {
var sum = 0
for (e in 1..it) {
sum += e
}
sum
}
// Special technique for passing to a higher-order function
map(listOf(1, 2, 3)) {
it + 1
}
Kotlin function literals are called closures. In other words, they can resolve variables other than the variables passed as arguments from the scope where the code was written. It is easier to understand with a simple code example than with text, so let’s look at one.
The following example is a function named counter that returns a closure.
fun counter(): () -> Int {
var count = 0
return {
count++
}
}
This function returns a “function that returns an Int.” Let’s call the “function that returns an Int” function A. Function A is { count++ }. The variable count is declared outside function A, but it can be referenced and updated from where function A is defined. Calling function A returns the value of count, and count increases by 1.
An example of using the counter function is shown below. It increases by 1 each time it is called.
val counter1 = counter()
println(counter1()) // => 0
println(counter1()) // => 1
println(counter1()) // => 2
val counter2 = counter()
println(counter2()) // => 0
println(counter2()) // => 1
Function A is obtained by counter(). You can see that the returned value increases each time function A, here declared as the variable counter1, is called.
Difference Between Lambda Expressions and Closures
What is the difference between a lambda expression and a closure? First, both lambdas and closures are anonymous blocks of specific functionality.
The difference is that a closure references external variables, while a lambda references only parameters.
Closure example: References an external variable named count
val count = 1
fun increase(): Int {
return count + 1
}
Lambda example: Receives a parameter named count from outside
fun increase(count: Int): Int {
return count + 1
}
A closure has external dependencies, while a lambda is similar to a static method without external dependencies.
Creating Anonymous Functions
A simple example of using an anonymous function is as follows. An anonymous function refers to a function defined without a name, such as fun(str: String).
fun main() {
// Create an anonymous function and assign it to greeting.
val greeting1 = fun() { println("Hello world!") }
// Call the anonymous function.
greeting1()
}
Output:
Hello world!
In the example above, an anonymous function is assigned to the greeting variable, and that anonymous function variable is called.
Using a lambda, an anonymous function can be defined more simply. The example below rewrites the previous example with a lambda.
fun main() {
val greeting2: () -> Unit = { println("Hello world!") }
greeting2()
}
Output:
Hello world!
Anonymous Function That Receives Arguments and Returns a Value
The following example is an anonymous function that receives arguments and returns a value.
fun main() {
val greeting3 = { name: String, age: Int -> "Hello. My name is $name. I'm $age year old." }
val result3 = greeting3("devkuma", 21)
println(result3)
}
Output:
Hello. My name is devkuma. I'm 21 year old.
On the left side of ->, the argument names (name, age) and data types (String, Int) are specified. On the right side of ->, the return value ("Hello My name...") is written.
return is implicit and is not written.
Anonymous Function Where Argument Types Can Be Omitted
The following example is an anonymous function where argument types are omitted.
fun main() {
val greeting4: (String, Int) -> String = { name, age -> "Hello. My name is $name. I'm $age year old." }
val result4 = greeting4("devkuma", 21)
println(result4)
}
Output:
Hello. My name is devkuma. I'm 21 year old.
In the example above, the greeting4 variable defines the function type as (String, Int) -> String, so the anonymous function can omit argument types and write only name, age.
Anonymous Function Where the Argument Name Can Be Omitted
If an anonymous function has one argument, the argument name can be omitted.
The example below is an anonymous function that receives one name variable.
fun main() {
val greeting5: (String) -> String = { name -> "Hello. My name is $name." }
val result5 = greeting5("devkuma")
println(result5)
}
Output:
Hello. My name is devkuma.
When there is explicitly only one variable, such as name, the variable name can also be omitted. If it is omitted, it must be accessed with the name it, an abbreviated form of iterator. An example is shown below.
fun main() {
val greeting6: (String) -> String = { "Hello. My name is $it." }
val result6 = greeting6("devkuma")
println(result6)
}
Output:
Hello. My name is devkuma.
In the example above, name is omitted, and it is used instead of name.
All of these could be omitted because the argument and return types were already defined in the variable. Since the compiler knows all this information, no build error occurs even if they are omitted.
Anonymous Functions Used in Libraries
In Kotlin, many objects have functions that receive functions as arguments. Using anonymous functions makes it easy to implement them with short code as follows.
fun main() {
val numbers = listOf(5, 1, 3, 2, 9, 6, 7, 8, 4)
println(numbers)
val sorted = numbers.sortedBy { it }
println(sorted)
val biggerThan5 = numbers.sortedBy { it }.filter { it > 5 }
println(biggerThan5)
}
Output:
[5, 1, 3, 2, 9, 6, 7, 8, 4]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[6, 7, 8, 9]
The anonymous function in sortedBy { it } is { it }. Looking at the definition of sortedBy, the argument is defined as (T) -> R?, and because numbers is already known to be Int, it can be omitted.
public inline fun <T, R : Comparable<R>> Iterable<T>.sortedBy(crossinline selector: (T) -> R?): List<T> {
return sortedWith(compareBy(selector))
}
Inside .filter { it > 5 } is also an anonymous function.
Using anonymous functions this way has the advantage of making code concise because duplication can be removed.
SAM (Single Abstract Method)
SAM (Single Abstract Method) means that there is only one abstract method. A SAM anonymous object can be created with a lambda expression and passed as an argument, which is similar to an anonymous function.
The code below is an anonymous object pattern commonly used in Android, where setOnClickListener receives an anonymous class as an argument.
In the anonymous class code, the actual implementation code is only doSomething, but boilerplate code must be written to define the class.
button.setOnClickListener(object : OnClickListener{
override fun onClick(view: View){
doSomething()
}
})
Using a lambda, only the implementation code can be written and passed as an argument. Since the argument type is already defined, writing only the lambda expression matching the type lets the compiler create an anonymous object automatically.
The code below is an example of creating an anonymous-class argument with a lambda.
fun setOnClickListener(listener: (View) -> Unit)
The function above can be called as follows.
button.setOnClickListener({ view -> doSomething() })
Since the compiler already knows the argument type here, the argument can be omitted as follows.
button.setOnClickListener() { doSomething() }
Also, if the argument is an anonymous function or anonymous class, the function parentheses (()) can be omitted as follows.
button.setOnClickListener { doSomething() }
Removing the parts that can be omitted makes the code much more concise.
In summary, as shown above, this syntax makes the code look like the implementation body of the function rather than an anonymous function being passed as an argument, which can improve readability. However, depending on the function structure, it can also reduce readability, so it should be used carefully.