Scope Functions

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

The five scope functions run a lambda with an object in scope. They differ only in whether the object is it or this inside the lambda, and whether the call returns the object or the lambda's result.

The five scope functions
Function Object as Returns Typical use
let it lambda result ?.let on a nullable
run this lambda result configure, then compute
with(x) this lambda result calls on one object
apply this the object configure an object
also it the object side effects, logging
Each scope function once, on the catalog (Scope.kt)Kotlin
class CartLine { var bookId = 0; var qty = 1 }
fun main() {
  println(books.find { it.id == 5 }?.let { "${it.title}: $${it.price}" } ?: "not found")
  val line = CartLine().apply { bookId = 2; qty = 3 }        // returns the object
  println("subtotal " + with(books.first { it.id == line.bookId }) { price * line.qty })
  println(line.run { "cart line: book #$bookId x $qty" })    // returns the lambda result
  val evenIds = books.map { it.id }
    .also { println("all ids $it") }                         // side effect, passes on
    .filter { it % 2 == 0 }
  println("even ids $evenIds, top rated ${books[2].rating.takeIf { it >= 4.5 }}")
}
Output
The Clockmaker's Paradox: $16.2
subtotal 118.5
cart line: book #2 x 3
all ids [1, 2, 3, 4, 5, 6]
even ids [2, 4, 6], top rated 4.8

takeIf returns the value when the predicate holds and null otherwise, so it pairs with ?.let. All of them are inline, so they cost no lambda allocation. The pitfall is nesting: inside an apply within a run, this is the innermost receiver. Use one scope function per expression, and name the parameter (let { book -> }) when nesting is unavoidable.