Lambda Expression and Anonymous Functions in Kotlin

Learn via video courses
Topics Covered

Overview

Lambda expressions and anonymous functions are fundamental concepts in Kotlin (and many other programming languages) that enable you to define and use functions in a more concise and expressive manner. They are particularly useful when you need to pass behavior as an argument to another function or when you want to define small, one-off functions without explicitly giving them a name.

Introduction

Lambda expressions and anonymous functions are important concepts in Kotlin that allow you to create small, inline functions without the need for a full function declaration. A lambda expression in Kotlin is a shorter way to represent a function. It is used to pass the behavior as the argument to a higher-order function. On the other hand, anonymous functions in Kotlin are similar to lambda expressions but with more explicit syntax and the ability to include multiple return statements. They are declared using the fun keyword, but without providing a name.

Lambda Expression

Lambda expressions in Kotlin are a concise way to define and pass around small blocks of code or functions as values. They are essentially anonymous functions that can be used to create instances of functional interfaces (interfaces with a single abstract method), often to pass them as arguments to higher-order functions or use them in functional programming constructs.

Syntax

Lambda expressions in Kotlin are defined using the following syntax:

KeywordDescription
valThis keyword declares an immutable variable (value) in Kotlin.
lambdaNameThis is the name you assign to the variable that will store the lambda function.
TypeThis indicates the type of the variable lambdaName. In the context of lambda expressions, the type usually specifies the expected parameter types and the return type of the lambda. For example, (Int, Int) -> Int signifies a lambda that takes two Int parameters and returns an Int.
argumentListThis section lists the parameters that the lambda function accepts. It could be one or more parameters, depending on the function's needs. These parameters are used in the codeBody to perform computations or operations.
codeBodyThe codeBody is the heart of the lambda expression. It contains the code that defines the behavior of the lambda function. It can be a single expression or a block of statements enclosed in curly braces. The result of the last expression (or explicitly returned expression) within the codeBody becomes the return value of the lambda function.

Example:

  • lambdaName is add.
  • Type is (Int, Int) -> Int, indicating a lambda that takes two Int parameters and returns an Int.
  • argumentList is (a, b).
  • codeBody is a + b, which calculates the sum of the two parameters.

Passing Trailing Lambdas

Trailing lambdas in Kotlin allows you to put a special kind of code block, called a "lambda expression," outside of the parentheses of a function call if it is the last argument of the function. This can enhance code readability, especially when working with higher-order functions. Trailing lambdas are particularly useful in situations when the functions use many functional programming constructs, such as map, filter, and forEach, or when the lambda expression is long and contains multiple lines of code. They help keep the code clean and focused on the logic you're implementing.

Example:

Output:

Explanation:

In the above code, we've used the map function to create a list of name lengths from the list of names. The trailing lambda simplifies the code by moving the transformation logic outside the parentheses of the map function call, while still explicitly naming the lambda parameter.

it: Implicit Name of A Single Parameter

When you're working with lambda expressions that have only one parameter, Kotlin allows you to use the implicit name it to refer to that parameter without explicitly specifying its name. This becomes convenient when the lambda's body is concise and the parameter's name is not necessary for understanding the code.

Here's an explanation of how it works:

  • Single Parameter Lambda:
    When a lambda contains a single parameter, you can use the it keyword to refer to that parameter within the lambda's body.
  • Concise Usage:
    This is especially useful when the lambda's body is short and the parameter's name doesn't add much value to the readability of the code.
  • Implicit Scope:
    The implicit it parameter is only available within the lambda's scope. If you need to refer to multiple parameters or use the parameter outside the lambda's body, you should explicitly name the parameter.

Code:

Output:

Explanation:

In the above code, the lambda passed to the map function takes a single parameter (each number in the list), so we can use the implicit it parameter to refer to that number directly.

Returning a Value from Lambda Expression

Lambda expressions in Kotlin can also return values. When a lambda is used in a context where a value is expected (such as with functions that take lambdas as arguments and return values based on those lambdas), you can specify what the lambda should return using the return keyword or simply by providing an expression as the last statement in the lambda.

Here's an example of using a lambda expression to return values:

Code:

Output:

Explanation:

In the above code, the map function takes a lambda that transforms each element in the numbers list. The lambda { number -> number * 2 } returns the result of multiplying each number by 2. The map function collects these transformed values into a new list (doubledNumbers).

This mechanism can also be used to return values from various lambda-based operations, such as mapping, filtering, or any other functional programming constructs.

Underscore for Unused Variables

When you have a lambda expression and you don't need to use any of the variables in it, you can just use the underscore (_) as a placeholder for those unused variables. This way, you can ensure the variable is ignored and the compiler won't warn you about it.

Here's an example demonstrating the use of an underscore for a lambda expression.

Code:

Output:

Explanation:

In the above code, we have used the map function to map every number in the list to a single integer value of 4242. Since we have not actually used the number value inside lambda, we can substitute it with an underscore _ parameter.

Using the underscore as a placeholder helps to make your code cleaner and more readable, indicating to others (and to the compiler) that the variable is intentionally ignored and not used within the lambda expression.

Destructuring in Lambdas

Destructuring is a technique in Kotlin that allows you to break down the contents of a data structure (like a data class, a tuple, or a pair) into its individual variables. This technique is especially useful when dealing with lambda expressions. It allows you to extract the value of a complex object passed to a lambda expression and work with that value individually. Here's how destructuring can be used in lambda expressions:

Code:

Output:

Explanation:

In the above code, we have used the map function to associate each individual in the list with their respective name. Since we only require the names and not the ages within the lambda, we have used the destructuring technique (name, _) as the lambda parameter. This allows us to focus solely on extracting the name value and disregarding the age.

Type Inference vs Type Declaration

Type Inference:

Type inference is a compiler feature that allows it to infer the data type of an expression or variable based on its initial value. In other words, you don’t have to explicitly specify the type of the variable or expression; the compiler will figure it out for you.

Type Declaration:

A Type declaration is a statement that specifies the type of data that a variable or expression will have. It can be used to provide clarity or to control the data type. However, it is important to note that Type Declarations may not always yield the expected result.

Comparison:

Type InferenceType Declaration
It saves you from explicitly stating types when the compiler can determine them from the context. This helps in reducing verbosity and writing more concise code. However, the actual type may not always be clear to others.Type declarations provide clarity to both the programmer and the compiler. They avoid unexpected type-related problems and make the code understandable, especially in situations where type inference may lead to ambiguities.

Anonymous Function

An anonymous function in Kotlin is a way to define a function without explicitly naming it. It is often used as a concise replacement for lambda expressions, especially when you need to specify the return type explicitly.

Closures

Closures are a concept in programming languages where a function can capture and remember the variables from its surrounding scope even after that scope has finished executing. In Kotlin, both lambda expressions and anonymous functions can create closures.

Code:

Output:

Explanation:

In the above code, the closure incrementByTwo captures the total variable from the outer scope and increments it each time it's invoked.

Function Literals with Receiver

Function literals with receiver, often referred to as "extension function literals," allow you to define a block of code as an extension function for a specific receiver type. The this keyword within the block refers to the receiver object.

Code:

Output:

Explanation:

In the above code, the concat function literal is an extension function for StringBuilder. It uses the this keyword to refer to the StringBuilder instance on which it's invoked.

Difference Between Lambda Expression and Anonymous Function

The key differences between lambda expression in Kotlin and anonymous function in Kotlin are as follows:

AspectLambda ExpressionAnonymous Function
SyntaxLambda expressions have a concise syntax that uses curly braces and an arrow ({ parameters -> body }). This makes them visually compact and suitable for simple expressions.Anonymous functions have a slightly more verbose syntax that includes the fun keyword, parameter list, and return type declaration (fun(parameters): returnType { body }). This syntax is often used when more complexity is involved.
Return Type DeclarationThe return type of a lambda expression is optional. It can be inferred from the body of the expression or explicitly declared if needed.Anonymous functions require an explicit return type declaration. This makes them suitable when you need to ensure a specific return type, especially for complex operations.
Parameter DeclarationsLambda expressions can use implicit parameter names, often represented as it, when there's only one parameter. They can also explicitly name parameters.Anonymous functions require explicit naming of parameters and their types. This provides a clear indication of the parameter names and types, even in cases of multiple parameters.
UsageLambda expressions are commonly used in functional programming constructs like higher-order functions (map, filter, etc.). They are well-suited for concise operations and transformations.Anonymous functions are chosen when you need to declare a specific return type and for more complex logic. They are used in scenarios where a lambda expression might not provide enough flexibility.
ExtensibilityLambda expressions are often limited to single expressions. They work best when the logic can be expressed in a concise manner.Anonymous functions can include multiple expressions within their bodies, allowing for more complex behaviour. This makes them suitable for situations where a code block with explicit returns is necessary.
Code BlockLambda expressions don't necessarily require a code block when they involve a single expression. The result of the expression becomes the result of the lambda.Anonymous functions require a code block, which includes the function's logic, and an explicit return statement to specify the return value.
Exampleval double: (Int) -> Int = { it * 2 }val sum: (Int, Int) -> Int = fun(a, b) { return a + b }

Conclusion

  • Lambda expressions and anonymous functions allow programmers to write short, efficient, and inline functions. These features make code easier to read and maintain, making functional programming more like regular programming.
  • Both lambda expressions and anonymous functions treat functions as first-class citizens. This means they can be passed as arguments, returned from functions, and stored in variables—enabling higher-order functions and flexible code design.
  • Passing a trailing lambda to a function simplifies the syntax by removing unnecessary parentheses.
  • Destructuring within lambdas simplifies the extraction of components from complex data structures. This leads to cleaner and more expressive code, making it easier to work with composite types like data classes and tuples.
  • Both lambda expressions and anonymous functions have closure properties, enabling them to access variables from their surrounding scopes.