10 minutes read

We already know how to declare functions with fixed names. Now, let's find out the last first-class-citizen feature: the opportunity to create a function at runtime and without a predefined name. That's lambda! Lambda is one of the most important features and is used profusely in modern programming.

Functions without names

To create a Kotlin function that isn't bound to its name, you can use either an anonymous function or a lambda expression:

  • fun(arguments): ReturnType { body } – this is commonly called an "anonymous function".

  • { arguments -> body } – this is commonly called a "lambda expression".

To better understand it, take a look at the example below. Here, two functions are declared: they are declared in different ways, but they do the same thing:

fun(a: Int, b: Int): Int {
    return a * b
} // normal function but no name 

{ a: Int, b: Int -> a * b } // we shifted the parameter or argument in the curly braces

As you see, they compute the multiplication of two numbers.

Both these functions have a reasonable type: (Int, Int) -> Int. So, types work here just the way they do for top-level functions discussed in previous topics.

Note that if you want to declare a lambda without arguments, you do not need to write the "arrow symbols". A lambda without argument definition looks like this: { body }.

You may ask: how can we use a function without a known name? The answer is: there are several options.

For example, you can assign the function to a variable and then invoke it by invoking the variable:

val mul1 = fun(a: Int, b: Int): Int {
    return a * b
}

val mul2 = { a: Int, b: Int -> a * b }

println(mul1(2, 3))  // prints "6"
println(mul2(2, 3))  // prints "6" too

Also, you can pass such a function as an argument or return such a function from another function.

Finally, you can place parentheses with desired arguments right after the function definition to invoke it in place. However, that doesn't make much sense. So, mostly the first three described options are used.

The process of creating these two functions is quite similar, but lambdas have a more concise and convenient syntax. Therefore, lambdas are almost always used to create a function at runtime in real life. Moreover, there are programmers who don't stick to Kotlin official naming rules and can say "an anonymous function" instead of "a lambda". Despite the fact that everybody understands them, we suggest that you use the proper naming.

For the same reason of convenience, now we will talk only about lambdas.

Lambdas and syntactic sugar

There are ways to make code more readable for human beings without changing the code logic. If there is such a way in a programming language and it relates to syntax, we call it syntactic sugar. Kotlin promotes Functional Programming so there is syntactic sugar for it.

Let's recall this example of passing a function as an argument:

fun isNotDot(c: Char): Boolean = c != '.'
val originalText = "I don't know... what to say..."
val textWithoutDots = originalText.filter(::isNotDot) 
println(textWithoutDots) // I don't know what to say

Simply put, we have created the function isNotDot, which returns Boolean and then uses originalText.filter, which will iterate over every char in the string, apply the isNotDot function, and then return a string without any dots. The filter excludes any char in the string that returns false.

Just in case, there is a specific topic for filtering elements in collection.

Now, let's rewrite it to pass a lambda:

val originalText = "I don't know... what to say..."
val textWithoutDots = originalText.filter({ c: Char -> c != '.' })
println(textWithoutDots) // I don't know what to say

It works! First of all, we don't need to specify a function and then take reference from it.

Kotlin infers types of many objects, and here specifying the c type isn't necessary:

originalText.filter({ c -> c != '.' })

Second, there are situations when the lambda is passed as the last argument. In such a case, Kotlin provides a way to eliminate bracket sequences ({ }) and write the lambda outside the parentheses:

originalText.filter() { c -> c != '.' }

If the parentheses are left empty after that operation, you can remove them:

originalText.filter { c -> c != '.' }

Please note that sometimes function reference is more readable than a lambda, and there is no right answer which one is preferable. However, if the code is quite complex, instead of copy-pasting some lambda, it may be better to use function reference for easier maintenance and reusability.

Implicit name of a single parameter: it

Finally, when there is a single parameter in a lambda, there is an opportunity to skip it. The parameter is available under the it name. The type of it is inferred from the type of the argument being passed to the lambda. The final version of the code that removes dots is this:

val originalText = "I don't know... what to say..."
val textWithoutDots = originalText.filter { it != '.' }

Pretty impressive, huh?

Returning from a lambda expression in Kotlin is achieved using return@label, where label is a tag, usually matching the name of the higher-order function in the context of which the lambda was called. For example, when using someLambda as a lambda expression in a higher-order function, the return from the lambda would look like return@someLambda.

This is particularly useful in cases where the lambda is used in functions such as forEach, map, let, and so on. Returning with return@label allows for exiting the lambda expression without interrupting the execution of the outer function.

Here is an example of using return@label in Kotlin:

listOf(1, 2, 3, 4).forEach { 
    if (it == 3) return@forEach  // Skipping number 3
    println(it)
}
println("End")

In this example, when it equals 3, the lambda expression is interrupted and continues with the next iteration of the forEach loop. println("End") will execute after the loop completes.

Conclusion

Lambda expressions are one of the most important and powerful features in Kotlin and in functional programming. In this topic, we've learned what anonymous functions and lambdas are. Lambdas help us create functions at runtime. This is convenient when calling functions from Kotlin standard library (e.g., those processing data), as it helps reduce code length. Finally, we've hopefully convinced you that functions are first-class citizens in the Kotlin language.

140 learners liked this piece of theory. 9 didn't like it. What about you?
Report a typo