Simple Way to Understand Kotlin Scope Functions - let, run, with, apply, also
Tags: kotlin, beginners, android, programming
Published: 2022-01-29 00:33:49

**You have no idea when to use them because they are confusing. This article explains the common practical usages of these Scope functions.**

If you look at the scope function examples in my previous post [here](https://vtsen.hashnode.dev/complete-c-to-kotlin-syntax-comparisons#heading-let-run-with-apply-also), you can clearly tell that all those Kotlin Scope Functions (i.e. `let`, `run`, `with`, `apply`, `also`) can accomplish the same thing. They're all interchangeable, with slightly different in syntax. 


## Syntax Differences
Let's understand the syntax differences between them first. 

![simple_way_to_understand_kotlin_scope_functions_01.JPG](https://cdn.hashnode.com/res/hashnode/image/upload/v1635657504610/lygWZ0mFp.jpeg)
*Source: [kotlinlang.org](https://kotlinlang.org/docs/scope-functions.html#function-selection)*
>**Note 1:** All these scope functions are extension functions except for `run` and `with`.  There are 2 `run`scope functions. One is with extension and another one without the extension function. 

```kotlin
val str = "test"
// let, run, also, apply - are "extension functions"
str.run {
    println(this)
}

// with - is NOT an "extension function"
with(str) {
    println(this)
}

// run - could be NOT an "extension function"
run {
    println(str)
}
```

>**Note 2:** Return value is **lambda result** means return the **last line returned value** in the lambda expression.  All scope functions return value is lambda result except for `apply` and `also` which **context object** is returned.

```kotlin
val str = "test"

/* let, run, with -  return last returned value in the lambda expression */
val tmp1 = str.run {
    // return Kotlin.Unit (equivalent to void in Java)
    println(this)
}

// this will print out "Kotlin.Unit"
println(tmp1)

// apply, also -  always return the object context (i.e. str)
val tmp2 = str.apply {
    println(this)
}

// tmp2 is str, this will print out "test"
println(tmp2)
```

>**Note 3:** Within the scope, we can reference the object either through `this` or `it`. 
- `run`, `with` and `apply` use `this`. 
- `let` and `also` use `it`

```kotlin
val str = "test"

/* run, with, apply - object reference is "this" */
str.run {
    println(this)
}

// let, also - object reference is "it"
str.let {
    println(it)
}
```

## It is confusing...
I must admit, trying to understand all these differences make me even more confused! This is because the syntax itself doesn't explain when and how exactly we should use them?

The crucial part is **not the syntax but their usages** but how and when developer usually use them? Let's look at more practical usages of them in the following!

Let's assume we have the following class.

```kotlin
class Dog(val name: String) {
    var age: Int = 0
    var owner: String? = null

    fun bark() = println("$name is barking")
    fun eat() = println("$name is eating")
}
```

## When to Use `let`?
`let` is typically used for nullable object to avoid `NullPointerException` 

**Replace null check**
```kotlin
var nullableDog: Dog? = null
if (nullableDog != null) {
    nullableDog.age++
    nullableDog.owner = "Vincent"
    nullableDog.bark()
    nullableDog.eat()
}
val age = nullableDog?.age
```
**with `let`**
```kotlin
var nullableDog: Dog? = null
val age = nullableDog?.let {
    it.owner = "Vincent"
    it.bark()
    it.eat()
    it.age++
}
```

`it` can be replaced by any name for readability.  This is especially useful if you have **chained / nested scope functions,** where the name can be used to differentiate between inner vs outer scope. 

```kotlin
val age = nullableDog?.let { myDog ->
    myDog.owner = "Vincent"
    myDog.bark()
    myDog.eat()
    myDog.age++
}
```
> **Note:** This example above is NOT a nested scope function. I personally hate it and will avoid it

If you don't need this readability, **`let` is pretty useless** in my opinion because it can be completely replaced by `run` and you don't need to specify the `it` to reference the context object.


## When to Use `run`?
`run` usage is similar to `let` which makes your code readable to perform the null check. Since `this` can be omitted in the lambda expression, I feel that the code is cleaner. Thus, I personally would prefer `run` over `let`. 

```kotlin
var nullableDog: Dog? = null
val age = nullableDog?.run {
    owner = "Vincent"
    bark()
    eat()
    age++
}
```

## When to Use `run` Non-extension Function?
`run` with non-extension function is very similar to standard scope function in other languages. `dog` variable in the inner scope refers to `Jimmy` and `dog` variable in the outer scope refers to `Lily`

```kotlin
val dog = Dog("Lily")

run {
    val dog = Dog("Jimmy")
    println("Dog name is ${dog.name}")
}

println("Dog age is ${dog.name}")
```

> **Note:** This is probably rarely used. I haven't seen any code that uses this. Separating out to a different function is probably a better idea than using the `run` non-extension function, scope function.


## When to Use `with`?
`with` is similar to `run` except it is used when you want to operate on a non-null object. If your object is nullable, do NOT use `with`.

```kotlin
val dog = Dog("Lily")

val age = with(dog) {
    owner = "Vincent"
    bark()
    eat()
    age++
}
```

## When to Use `apply`?
`apply` is usually used to initialize the properties in the class during the object creation. 

** You replace this**
```kotlin
val dog = Dog("Lily")
dog.owner = "Vincent"
dog.age = 10
```

** with**
```kotlin
val dog = Dog("Lily").apply {
    owner = "Vincent"
    age = 10
}
```

## When to Use `also`?
`also` is pretty much the same as `apply` and we rarely need it. Similar to `let`, it is probably needed for readability issue in nested scope functions, where you can replace `it` with a more meaningful name.

```kotlin
val dog = Dog("Lily").also {
    it.owner = "Vincent"
    it.age = 10
}
```

## Summary

This is how I'm going to use these scope functions:

- **let** - do NOT use this for null check unless for readability issue in nested / chained scope functions
- **run** - use this for null check instead. However, if you need to use `this` for object reference, consider to use `let` instead for readability
- **run (non-extension function)** - probably NOT required, replace this scope with a function call instead for better readability
- **with** - use this for non-nullable object
- **apply** - use this for object creation and initialization
- **also** - do NOT use this, unless for readability issue in nested scope function. Use `apply` instead.


The purpose of scope function is to **reduce code and make your code readable**. So, it is really up to you how you would like to use them to achieve it. Sometimes this could be just a personal thing. 

For example, what I suggested here (i.e. do NOT use `let` for null check) probably goes against the majority, but I find `run` is more straight forward. It makes my options simple. I just need to choose between `run` and `with`
- use `run` for nullable variable 
- use `with` for non-nullable variable. 

Isn't this simple? 

Scope function is a nice-to-have feature. Even if you don't use them, it is fine too as long as your code is still readable. 

---

*Originally published at [https://vtsen.hashnode.dev](https://vtsen.hashnode.dev/simple-way-to-understand-kotlin-scope-functions-let-run-with-apply-also)*.