Kotlin Lambda Expressions

An Introduction to Simplifying Your Code

Introduction

Lambda expressions are a powerful and concise tool that has gained significant importance in modern programming, particularly in functional programming paradigms. In this tutorial, we will delve into the fundamentals of Kotlin lambda expressions, exploring their syntax, usage, and the numerous benefits they offer. Additionally, we will cover common use cases and best practices for effectively working with lambda expressions. Even if you are unfamiliar with terms like "anonymous functions" or "higher-order functions," there's no need to worry, as I will provide explanations and demonstrations throughout the article. The objective is to provide a comprehensive and easily understandable introduction to lambda expressions in Kotlin and highlight how they can significantly simplify your code. While a basic understanding of concepts such as functions, parameters, and return types would be helpful in comprehending the code snippets, I have included additional resources for your reference.

What Are Lambda Expressions in Kotlin?

Lambda expressions, also known as anonymous functions, arrow functions, fat arrow functions, closures, functional interfaces, or functional blocks in other programming languages, serve the same purpose as lambda expressions in Kotlin. They offer a concise and efficient way to create small, reusable functions that can be assigned to variables or passed as arguments.

In Kotlin, lambda expressions are anonymous functions that act as values, resulting in more concise and efficient code. They're especially useful for creating small, reusable functions that can be easily passed as arguments or assigned to variables.

"Lambda expressions are anonymous functions? "

confused gif via GIPHY

via GIPHY

Don't be confused. In the next few paragraphs, I'll be explaining what anonymous functions are and all other related concepts needed to understand Lambda expressions.

Definition Of Related Terms

Before advancing, it's worth noting that functions are treated as first-class in Kotlin, which is not always the case in other programming languages. This means that functions are treated as values, similar to integers or strings. Consequently, they can be passed as arguments to other functions, returned as values from functions, and stored in variables or data structures, just like any other object.

This powerful feature of treating functions as first-class citizens in Kotlin allows for more concise and efficient code, as well as greater flexibility in designing complex software systems. The Kotlin documentation provides more information on how to utilize this feature.

Anonymous Functions

Anonymous functions in Kotlin are functions without a name. They are defined using the fun keyword, followed by the function parameters, a return type (if any), and the function body.

Anatomy of an anonymous function

They are used to perform a specific task but don't need to be referenced later in the code. Here are a few use cases:

  1. Anonymous functions can be used as an argument to other functions.
val numbers = listOf(1, 2, 3, 4) 
numbers.forEach(fun(it: Int) { println(it) })

Explanation: The anonymous function is being passed as an argument to the forEach function, which is called on the numbers list. The fun keyword is used to define an anonymous function and (it: Int) is the function's parameter. In this case, the anonymous function takes a single parameter it of type Int, and it simply prints the value of it using the println function.

2. Anonymous functions can be assigned to a variable

val subtract = fun(a: Int, b: Int) = a - b  
println(subtract(6, 3)) // 3

So now that you understand what anonymous functions are, I'm sure you are curious to know the difference between a lambda expression and an anonymous function.

The key difference between an anonymous function and a lambda expression is that a lambda expression is a concise way of representing anonymous functions in Kotlin.

Basic syntax and structure of lambda expressions

The syntax for lambda expressions in Kotlin is shown below

{ arguments -> body }

A lambda expression takes one or more optional arguments as input. To make your code more readable, the type of the input parameter has to be explicitly specified. An arrow and a body wrapped in curly braces {} follows next. The return type of the entire body is inferred from the expression in the body.

Here's an example:

{ a: Int, b: Int -> a + b }

Explanation: The lambda function takes two input parameters a and b, and returns their sum. We can go further by assigning this sum to a variable and calling it like a function.

val sum = { x: Int, y: Int -> x + y } 
val result = sum(5, 3) 
println(result) // Output: 8

NOTE: A lambda function can exist without arguments. In such a case, using the arrow -> syntax will be unnecessary. In the example shown below, the lambda expression println("Hi") takes no arguments and simply prints the string "Hi" to the console. This lambda function is assigned to the variable sayHi.

val sayHi = { println("Hi") }

This lambda function can be called later by invoking the sayHi variable as if it were a regular function, like this:

sayHi() //prints hi

Using lambda expressions as function arguments

Lambda expressions can also be used as arguments in higher-order functions (functions that take other functions as parameters).

Here's see how that works:

fun performOperation(x: Int, y: Int, operation: (Int, Int) -> Int): Int {
    return operation(x, y)
}

Explanation:performOperation is a higher-order function that takes two integer values x and y, and a third argument operation, which is a lambda expression that takes two integers and returns an integer. The function itself returns an integer, which is the result of invoking the operation lambda expression with the x and y values.

We can use this higher-order function with different lambda expressions that define different operations to perform on the x and y values. For example, we can define a lambda expression that adds two numbers:

val sum = performOperation(3, 5) { a, b -> a + b }
println("Sum: $sum") // Output: Sum: 8

Explanation: we invoke the performOperation function with the values 3, 5, and a lambda expression that adds the two values together. The result of this function call is stored in the variable sum, which is then printed to the console.

Similarly, we can define a lambda expression that multiplies two numbers:

val product = performOperation(3, 5) { a, b -> a * b }
println("Product: $product") // Output: Product: 15

Explanation: We invoke the performOperation function with the values 3, 5, and a lambda expression that multiplies the two values together. The result of this function call is stored in the variable product, which is then printed to the console.

Let's take one practical example

fun calculateSum(numbers: List<Int>, calculation: (List<Int>) -> Int): Int {
    return calculation(numbers)
}

val sum = calculateSum(listOf(1, 2, 3, 4, 5)) { numbers ->
    var sum = 0
    for (num in numbers) {
        sum += num
    }
    sum
}
fun main() {
println("The sum of the numbers is $sum")
}

Explanation: the calculateSum function takes a list of integers as its first argument, and a lambda expression that takes a list of integers and returns an integer as its second argument. The function invokes the calculation lambda expression with the list of integers and returns the result.

The sum variable is initialized by invoking the calculateSum function with a list of integers and a lambda expression that sums up the values in the list. The lambda expression uses a for loop to iterate over the list of numbers and adds them up to obtain the total sum.

Finally, the sum variable is printed out to the console, which will output the message: "The sum of the numbers is 15", since the sum of the integers in the list is indeed 15.

Using lambda expressions in collection processing

Collections are containers that hold a group of related objects or data items. The data held by Kotlin's built-in collection classes such as List, Set, Map, and Array, can be manipulted using lambda expressions.

  1. Filtering a Collection: The filter function in Kotlin can be used to filter elements in a collection based on a condition.
val numbers = listOf(1, 2, 3, 4, 5)
val evenNumbers = numbers.filter { it % 2 == 0 } 
// it here stands for each member of the collection
println(evenNumbers) // Output: [2, 4]
  1. Mapping a Collection: The map function in Kotlin can be used to transform each element in a collection.
val numbers = listOf(1, 2, 3, 4, 5)
val squaredNumbers = numbers.map { it * it }
println(squaredNumbers) // Output: [1, 4, 9, 16, 25]
  1. Finding an Element: The find function in Kotlin can be used to find the first element in a collection that matches a given condition.
val numbers = listOf(1, 2, 3, 4, 5)
val evenNumber = numbers.find { it % 2 == 0 }
println(evenNumber) // Output: 2
  1. Grouping a Collection: The groupBy function in Kotlin can be used to group elements in a collection based on a given key.
data class Car(val make: String, val model: String)

val cars = listOf(
    Car("Toyota", "Camry"),
    Car("Toyota", "Corolla"),
    Car("Honda", "Civic"),
    Car("Honda", "Accord"),
    Car("Ford", "Mustang"),
    Car("Ford", "Fiesta")
)

val carsByMake = cars.groupBy { it.make }
// Output

// {

//   Toyota=[Car(make=Toyota, model=Camry), Car(make=Toyota, model=Corolla)],

//   Honda=[Car(make=Honda, model=Civic), Car(make=Honda, model=Accord)],

//   Ford=[Car(make=Ford, model=Mustang), Car(make=Ford, model=Fiesta)]

// }

In all of these examples, the lambda expression is passed as an argument to the relevant collection processing function. The lambda expression is defined using the curly braces {} syntax and takes one or more parameters (denoted by it in the examples above) and returns a Boolean, a transformed value or any other object as per the requirements of the collection processing function.

Using lambda expressions for event handling

Lambda expressions are also handy for event handling in Kotlin since they allow defining callback functions within the code itself. For instance, one can use a lambda expression to indicate the action to be performed when a button is clicked with setOnClickListener.

button.setOnClickListener {
// execute some code when the button is clicked
}

The lambda expression can also receive arguments to modify the behavior of the callback function.

Conclusion

With the ability to use lambda expressions as function arguments, you can create higher-order functions that provide greater flexibility in designing complex software systems. By understanding the basics of lambda expressions and their syntax, you can begin to leverage their benefits in your own Kotlin projects. So go ahead and experiment with lambda expressions in your code and see how they can help you write more efficient and readable Kotlin code.

References

Kotlin Programming Language, Official Documentation. (kotlinlang.org/docs/home.html)