Skip to content
Scope functions: let, run, apply, also, with
step 1/7

Reading — step 1 of 7

Learn

~4 min readFunctions and Lambdas

Scope functions: let, run, apply, also, with

You have met one of these. In Null Safety, maybe?.let { println(it.length) } ran a block only when the value was there. let has four siblings, and real Kotlin is saturated with them — apply and also most of all.

All five run a block with your object in scope, and differ on exactly two points:

FunctionInside the blockGives back
letitthe block's result
runthisthe block's result
withthisthe block's result
alsoitthe object
applythisthe object

So the choice is two questions — do you want the object back or a value computed from it, and do you want to name it or let it be this?

Getting the object back: apply and also

apply sets something up: the block configures the object and the expression hands the object back. also returns it too but names it it, which is what lets you slip a side effect into a chain without breaking it.

fun main() {
    val report = StringBuilder().apply {
        append("total=19")
        append(" kept=2")
    }
    println(report.toString())

    val smallest = listOf(3, 1, 2).sorted()
        .also { println("sorted to " + it) }
        .first()
    println(smallest)

    //> total=19 kept=2
    //> sorted to [1, 2, 3]
    //> 1
}

Inside apply the receiver is this, so append needs no qualifier, and what comes back is the builder, not the last append's value. Hence the test for also: delete the link and the value must not change.

Getting a value back: let, run and with

let transforms — it is the object, the block's result is the answer. run is let with this instead of it, and with is run written as a call taking the object as an argument. run also has a receiverless form: a block that computes a value.

fun main() {
    val station = "oslo"
    val missing: String? = null

    println(station.run { toUpperCase() + " (" + length + ")" })
    println(with(station) { toUpperCase() + " (" + length + ")" })
    println(run { val a = 2; val b = 3; a * b })
    println(missing?.run { toUpperCase() } ?: "(skipped)")
    // println(with(missing) { toUpperCase() })
    // error: only safe (?.) or non-null asserted (!!.) calls are
    // allowed on a nullable receiver of type String?

    //> OSLO (4)
    //> OSLO (4)
    //> 6
    //> (skipped)
}

The commented line is the real difference: run is an extension function, so it safe-calls, while with takes its receiver as an argument that stays nullable inside the block.

Grader note: on this course's Kotlin 1.3.70 the spelling is toUpperCase(); uppercase() gives unresolved reference: uppercase.

The trap: nesting

Put an it block inside another and the inner one shadows the outer — both are spelled it, and the nearest wins:

fun main() {
    "ada".let { "lovelace".let { println(it) } }

    //> lovelace
}

The compiler accepts that without a word. Chained scope functions — x?.also { }?.let { } — stay readable, since each it belongs to one link. Nested ones do not; name the parameters, { v -> ... }.

Common mistakes

  • apply where you meant let"42".apply { toInt() } hands back the String, so + 1 prints 421, not 43.
  • An also the chain depends on — if removing it changes the result, it belonged in the chain.
  • with on a nullable — it is not an extension, so there is no ?.; use ?.run or ?.let.
  • Nesting two it blocks — the inner shadows the outer, silently.
  • Wrapping everythingval x = compute() beats compute().let { } when there is nothing to chain.

Your exercise

Reading Log gives each of the five a job. with(station) prints the header; each reading is parsed by toIntOrNull() and travels a chain — ?.also { } adds it to the total, ?.let { } turns it into ok 12, and ?: run { } counts the skip and hands back skip. The footer is a StringBuilder().apply { }.

The hidden tests catch two things: two unparseable readings must still print total=0 kept=0 skipped=2, and 008 parses to 8, so print the number rather than the text.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…