Let's Code Kotlin: Scoped Functions (let, apply, also, run)
Tags: kotlin, android
Published: 2021-01-25 17:34:07

---
title: Let's Code Kotlin: Scoped Functions (let, apply, also, run)
published: true
description: Scoped functions -- let, apply, also, and run -- can make your code more concise while providing you benefits and simplicity. They can look intimidating but don't be afraid! Dive in with me as I go over these tools.
tags: kotlin, android
canonical_url: https://www.gatling.xyz/blog/2021/01/25/kotlin-scoped-functions/
---

Kotlin offers a lot of tools to help make coding easier and simpler. At first glance, though, they can be a bit overwhelming and the exact usages of each can be confusing. I'm going to do my best to break down `let`, `apply`, `also`, and `run`.

The primary differences between these four extension functions boil down to two things: the way the context object is referenced inside of the scope (`this` vs `it`), and the return value of the lambda. That's it.

#`.let { }`
This is one of two scope extension functions that I use regularly. [I mentioned in a previous post](https://dev.to/gatlingxyz/makethechangept2-kotlin-properties-10ni) that I typically use `let` for null checks, performing some extra work on the context if the object is not null. Inside the scope, the context object is referred to as `it`.

```kotlin
val dateCompleted = book.dateCompletedTimestamp?.let {
			convertToDateString(it)
		}
```

It can also serve as a way of mapping one value to another, as `.let` returns the lambda result (whatever is the last line of the lambda). For instance, in our example, `convertToDateString` will turn that timestamp into a String. Since it's on the last line of the lambda, it will become the value of `dateCompleted`. If `.dateCompletedTimestamp` was null, then `dateCompleted` would be null as well.

#`.apply { }`
`apply` is another useful extension function that performs the exact opposite of `let`: the context object is referred to as `this` and the same object is returned. I typically use `apply` when initially creating an object. You could similarly just build your object as a data class or with a Builder, but for those that you can't or don't have control over this works beautifully.

```kotlin
fun createBookUpdatePayload(dateCompleted: Long, bookId: String, rating: Int): JsonObject {
	return JsonObject().apply {
		addProperty("date_completed", message)
		addProperty("book_id", bookId)
		addProperty("rating", rating)
	}
}
```

#`.also { }`
This one, according to the docs, seems to have a very limited "official" usage: it seems to be used primarily for logging within a chain. I believe I've used it similarly a few times. If you've used it another way, leave a comment so we all can get a better understanding of what `.also` can do. `.also` refers to its context object as `it` and it returns the object it references.

```kotlin
fun getLastCompletedBook(): Book {
		return allBooks
				.filter { it.dateCompletedTimestamp != null }
				.also { println("Number of books read: ${it.size}") }
				.sortedByDescending { it.dateCompletedTimestamp }
				.first()
				.also { println("Last completed book: ${it.name}") }
}
```

#`.run { }`
I'm going to be honest: I don't think I've used this one. Its context object is referred to as `this` and it returns the last line of the lambda. I also can't think of a reason to use this over something else. But there are still recommendations for it in the official documentation. It's supposed to be a cross between `.let` and `.apply`.

If I had to shove this into my fictional book app, here's how I'd do it:

```kotlin
fun postReadingUpdateToTwitter(bookId: Long, currentPage: Int): Complete {
	return bookService.run {
		val result = postUpdate(bookId, currentPage)
		if (result == Result.OK) {
				Completable.complete()
		} else {
				Completeable.error("Update error")
		}
	}
}
```

Interestingly enough, you can also use `run` as just a scope lambda. Meaning, without using it as an extension function.

```kotlin
val book = run {
	val bookName = "How to use Run"
	val bookId = 4815162342

	Book(name = bookName, dateCompletedTimestamp = null, bookId = bookId)
}

addBookToShelf(book)
```

My advice: skip this one. But if you've got a good example, let me know in the comments.

#Bonus: `with`
There's also apparently another non-extension scope function named `with`. You instead pass the object into it, and use it like you would `.apply`.

```kotlin
with(allBooks) {
	forEach { book ->
		println("Title: ${book.title}")
	}
}

val lastCompletedBook = with(books) {
  val filtered = filter { it.dateCompletedTimestamp != null }
  val sorted = filtered.sortedByDescending { it.dateCompletedTimestamp }
  sorted.first()
}
```

As with the non-extension `run`, I might skip this. But I'm sure I can find a use for it somewhere. Though, at that point, I'd probably just make a separate function and call that.

---

#Recap!
`.let` → Context object is `it`; returns the lambda result
`.apply` → Context object is `this`; returns the object
`.run` → Context object is `this`; returns the lambda result
`.also` → Context object is `it`; returns the object

And two non-extension functions:
`run` → no context object; returns the lambda result
`with` → pass in the context object, use with `this`; returns the lambda result

Whew. That's a lot but it's worth learning about them. Don't overuse them; it can be easy to throw everything into a scoped lambda, but if it hurts readability I would revert to a more verbose solution. There's plenty of [official documentation](https://kotlinlang.org/docs/reference/scope-functions.html) on them if you need more insight. Have some fun with them though and let me know what you create with them!