Scope Functions.
Scope Function in Kotlin.
This article was written by “Doaa Mosallam”
Examples were used to facilitate explanation and clarify the meaning further.
What are Scope Functions?
Scope functions are a collection of functions in Kotlin that provide a concise and convenient way to perform operations on objects within a specific context. They essentially create a temporary scope in which you can access and manipulate the object without having to explicitly refer to its name every time. This can lead to more readable and maintainable code.
Here’s a breakdown of the key points about scope functions:
- Purpose: Execute a block of code on an object.
- Benefits: 1. Improve code readability by reducing verbosity.
2. Avoid repetitive use of the object name within the code block.
- Common functions:
let: Often used for null checks and working with nullable objects.apply: Useful for configuring objects with multiple properties.run: Similar toletbut returns the result of the lambda expression.with: Primarily used for accessing properties of an object within the code block.also: Similar toapplybut returns the original object.
- apply:
apply is a scope function in Kotlin that allows you to concisely configure an object by executing a block of code within the context of that object. This means you can access and modify the object's properties directly without explicitly mentioning its name every time.
Benefits of using apply:
- Improved Readability:
applyreduces verbosity by eliminating the need to repeat the object name within the code block. This makes your code cleaner and easier to understand. - Convenient Object Configuration:
applysimplifies the process of setting up objects with multiple properties, especially when performing chained operations.
data class Person(var name: String, var age: Int)
fun main() {
val person = Person("", 0)
person.apply {
name = "Alice"
age = 30
}
println(person) // Output: Person(name="Alice", age=30)
}In this example, we create aPerson object with initial empty values. We then use apply to set the name and age properties within the code block. The apply function returns the modified person object, which is then printed.
val numbers = mutableListOf<Int>()
numbers.apply {
add(1)
add(2)
add(3)
}
println(numbers) // Output: [1, 2, 3]Here, we use apply to modify the numbers mutable list. We add elements (1, 2, 3) directly within the code block, achieving a more concise approach.
Key Points:
- The code block inside
applyhas the object itself (the receiver) as the implicit receiver, allowing direct access to its properties. applyreturns the modified object, making it useful for chaining operations if needed.
When to Use apply:
- You’re configuring an object with multiple properties.
- The code block primarily focuses on modifying the object’s internal state.
- You want to improve code readability and reduce verbosity.
2. let:
The let function in Kotlin is another powerful scope function used for working with nullable objects. It allows you to safely execute a block of code on an object only if it's not null. This is particularly helpful for avoiding NullPointerExceptions, which can crash your program.
Benefits of using let:
- Null-Safety:
letensures that the code block within it only runs if the object you're working with is not null. This prevents potential crashes due to accessing properties or methods of a null object. - Concise Null Checks:
letprovides a cleaner way to handle null checks compared to traditional if-else statements. - Chained Operations: You can chain multiple
letcalls together for more complex null handling scenarios.
val maybeString: String? = "Hello, world!"
maybeString?.let { string ->
println("String length: ${string.length}")
}
// If maybeString is null, nothing happens (no output)Converting String to Uppercase (Handling Null)
val name: String? = "Alice"
val upperName = name?.let {
it.toUpperCase() // Use "it" to refer to the non-null string
}
println(upperName) // Output: ALICE (if name is not null)
// // (or null if name is null)When to Use let:
- You need to perform operations on a nullable object but want to avoid NullPointerExceptions.
- You want to condense null checks into a more readable format.
- You’re working with a chain of nullable objects and need to handle nulls gracefully at each step.
3.also:
The also scope function in Kotlin is designed to perform actions on an object and then return the original object itself. It's particularly useful when you need to perform some side effects (like logging or printing) or additional configuration on an object before using it further.
Get Doaa mosallam’s stories in your inbox
Join Medium for free to get updates from this writer.
Benefits of using also:
- Concise Object Configuration:
alsosimplifies the process of setting up an object with multiple properties or actions, especially when chaining operations. - Improved Readability: It reduces verbosity by eliminating the need to repeat the object name within the code block, making your code cleaner and easier to understand.
- Side Effects:
alsoenables you to perform additional actions (logging, printing messages, etc.) on the object within the code block while still returning the object for further use.
data class Person(var name: String, var age: Int)
fun main() {
val person = Person("", 0)
val configuredPerson = person.also {
it.name = "Bob"
it.age = 35
println("Person configured: $it") // Side effect (printing)
}
println(configuredPerson) // Output: Person(name="Bob", age=35)
}In this example, we create a Person object initially with empty values. We then use also to set the name and age properties within the code block. The also block also prints a message with the configured object (side effect). Finally, the original modified person object is printed.
- Adding Elements and Printing List Size.
val numbers = mutableListOf<Int>()
val updatedList = numbers.also {
it.add(1)
it.add(2)
it.add(3)
println("List size after adding: ${it.size}") // Side effect (printing)
}
println(updatedList) // Output: [1, 2, 3]Here, we use also on a mutable list numbers to add elements (1, 2, 3) and print the list's size within the code block. The also block doesn't modify the return value (the list itself), but it provides a way to perform an additional action (printing) before using the updated list.
When to Use also:
- You’re configuring an object with multiple properties and want to perform additional actions (like logging) during setup.
- You want to improve code readability by reducing the need to repeat the object name in a sequence of operations.
- The focus of the code block is on modifying the object’s internal state and performing some side effects.
4. run:
The run scope function in Kotlin is similar to let but offers more flexibility. It allows you to execute a block of code with an object as the context and return the result of the last expression within the block. This makes it useful for performing operations on an object and potentially using the outcome of those operations.
Benefits of using run:
- Object-Based Operations:
runprovides a way to execute code within the context of an object, allowing you to access and manipulate its properties and methods. - Returning Values: Unlike
let, which focuses on null safety and doesn't modify the original object,runcan return a value based on the operations within the block. This makes it versatile for calculations, transformations, or creating new objects based on the original object. - Improved Readability: Similar to other scope functions,
runreduces verbosity by eliminating the need to repeat the object name within the code block.
val text: String = "Hello, world!"
val length = text.run {
length // Access the built-in length property
}
println(length) // Output: 13Here, we use run on a string text to calculate its length using the length property within the code block. The length property is the last expression, and its value (13) is returned by run.
data class Person(var name: String, var age: Int)
fun main() {
val person = Person("Alice", 25)
val modifiedPerson = person.run {
copy(age = age + 5) // Create a new Person with modified age
}
println(person) // Output: Person(name="Alice", age=25) (original)
println(modifiedPerson) // Output: Person(name="Alice", age=30) (modified)
}In this example, we use run to create a new Person object based on the original person object. Inside the code block, we use the copy function with a modified age value. Since copy returns a new object, that becomes the return value of run. This demonstrates how run can be used to perform operations and obtain a new result.
When to Use run:
- You’re working with an object and need to return a value based on operations within the scope.
- You want to create a new object derived from the original object by modifying its properties.
- The code block focuses on manipulating the object and returning a result based on those operations.
5. with :
The with scope function in Kotlin allows you to execute a block of code within the context of an object. It's primarily used for accessing and manipulating the object's properties or methods directly without explicitly mentioning the object name every time.
Benefits of using with:
- Improved Readability:
withreduces verbosity by eliminating the need to repeat the object name within the code block, making your code cleaner and easier to understand. - Focus on Object Properties:
withkeeps the focus of the code block on accessing and modifying the object's internal state. - Alternative to Other Scope Functions: While
applyandrunare often used for object configuration and returning values,withprovides a simpler approach when the primary goal is to access and modify properties within the object's context.
Example 1: Modifying Properties of a Person Object
data class Person(var name: String, var age: Int)
fun main() {
val person = Person("Bob", 30)
with(person) {
name = "Alice"
age = 25
}
println(person) // Output: Person(name="Alice", age=25)
}Here, we use with on a Person object to modify its name and age properties directly within the code block. Since with doesn't return anything, the main purpose is to update the object itself.
Example 2: Accessing File Properties
import java.io.File
fun main() {
val file = File("myfile.txt")
with(file) {
if (exists()) {
println("File exists: $name, size: ${length()}")
} else {
println("File does not exist: $name")
}
}
}In this example, we use with on a File object to access its properties (exists, name, and length) directly within the code block. The logic checks if the file exists and prints some information based on its state.
When to Use with:
- You’re working with an object and want to access or modify its properties directly within a concise code block.
- The code block focuses on manipulating the object’s internal state rather than returning a new value.
- You prefer a simpler syntax compared to
applyorrunwhen the main focus is on object properties.
Overall, scope functions are a powerful tool in Kotlin for making your code more concise and expressive, especially when working with objects.







