Prabhat Pandey

@prabhatsdp

27 Aug, 2023

Scope Functions in Kotlin: let, also, run, apply, and with

Hey there! Have you ever heard of scope functions in Kotlin? They’re pretty cool and can make your code more concise and readable. Let me explain.

Kotlin is a programming language that’s been gaining a lot of popularity because of its simplicity, cleanliness, and conciseness. It’s so much easier to use than Java, and it’s made the lives of developers so much easier.

The best part about Kotlin is that it brings a lot of developer-friendly features that help you write less code, and Scope Functions in Kotlin are one of them.

Scope Functions in Kotlin

So, the Kotlin standard library has several functions that allow you to execute a block of code within the context of an object. These functions are called scope functions in Kotlin. There are five scoped functions in Kotlin: let, run, with, also, and apply.

When you call one of these functions on an object with a lambda expression provided, it forms a temporary scope. Inside this scope, you can access the object without its name. This is pretty handy because it makes your code more readable and reduces the amount of boilerplate code you need to write.

Now, you might be wondering how these functions differ from each other. Well, they all perform the same action, which is to execute a block of code on an object. However, what sets them apart is how the object becomes available inside the block and what the result of the whole expression is.

Still not clear? Don’t worry, I am going to explain all of them one by one so that you can understand each scope function in Kotlin easily.

let in Kotlin

let is probably the most used scope function in Kotlin. It provides the calling object reference as a lambda parameter and returns the result of the last expression of lambda. If you don’t explicitly name the lambda parameter, you can access the context object using the implicit parameter name `it`.

As it returns the result of the last expression, you can use let to map one object to another. Also, if you want to introduce some temporary variables while mapping, you can define them inside the let code block. Those variables will not be available outside of the let block.

You can also use let as a null check scope function by appending ? and then calling let on any nullable object. The code will only be executed if the calling object is not null. If you want to handle the failed case, then you can chain the run scope function after the let block using the ?: (Elvis) operator.

Below is an example of how you can use the let in Kotlin to format the current date and time.

val uiDateTime = LocalDateTime.now().let {
    val formatter = DateTimeFormatter.ofPattern("MMMM d, yyyy, h:mm a")
    it.format(formatter)
}

apply in Kotlin

The apply scope function in Kotlin is a powerful tool for configuring objects in a concise and readable way. It can help you eliminate repetition and simplify your code.

The apply function is especially useful when you need to perform multiple operations on an object. It is also useful when you need to initialize and configure an object in a single expression. For example, you could use apply to create and configure a TextView with specific values like this:

val textView = TextView(this).apply {
    text = "Hello, World!"
    textSize = 18f
    setTextColor(Color.BLACK)
    setBackgroundColor(Color.WHITE)
}

run in Kotlin

The run is a scope function in Kotlin that allows you to execute a block of code in the context of an object, and return the result of that block.

The run scope function in Kotlin is similar to let in terms of the return value. It returns the result of the last statement of the lambda block. But it doesn’t take the context object as the argument “it but as the receiver “this“.

The run function proves particularly handy when you wish to execute a sequence of operations on an object and effortlessly obtain the final modified object without explicitly needing to return it.

You can also use the run scope function in Kotlin without requiring an object. In this case, it returns the last statement’s result.

Let’s delve into an example to better understand the workings of the run function:

fun main() {
    // With object
    val number = 42

    val result = number.run {
        println("Number is: $this")
        this * 2 // Result of this expression will be returned
    }

    println("Result: $result") // Output: Result: 84
    
    // Without object
    val message = run {
        val greeting = "Hello"
        val target = "world"
        "$greeting, $target!"
    }

    println(message) // Output: "Hello, world!"
}

The code demonstrates the Kotlin run function. We used it with and without an object to execute code blocks, providing scoping and concise results. It prints numbers in the first call and a message in the second.

with in Kotlin

The with scope function in Kotlin is used to execute a set of operations on an object within a specified context, without needing to repeat the object reference. It’s a concise way to access the properties and methods of an object and perform multiple operations on it without having to reference the object each time.

We mostly use with is when we have to call multiple functions of the same class. So, instead of referencing the class, again and again, we should utilize with and call all the functions inside the scope of the with lambda.

fun main() {
    val car: Car = Toyota()
    with(myCar) {
        startEngine()
        honkHorn()
        turnLeft(degree = 30)
    }
}

In the above code, inside the with block, the startEngine() method is invoked to initiate the engine, followed by the honkHorn() method. Finally, the turnLeft(degree = 30) method is also called in the same scope. Here, the with scope function eliminates the need to repeatedly mention the car on each method call, enhancing code readability and conciseness.

also in Kotlin

The Kotlin also scope function is somewhat similar to apply. The key difference is that it receives the calling object as a lambda parameter instead of this. It also returns the object itself after modifications if done any.

Let’s look at an explanation and examples:

fun main(args: Array<String>) {
    
    data class Cat(val name: String, val color: String)
    
    val catDescription: String

    val cat = Cat(name = "Phoenix", color = "White")
        .also { catDescription =  "${it.name}'s color is ${it.color}"}
    
    println(catDescription)
}

In the above example, we used the also scope function to instantiate our catDescription variable based on the values of the cat object. And since also returns the object itself, our cat holds the cat object.

Built by Prabhat Pandey
using ReactJS