Reading — step 1 of 7
Learn
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:
| Function | Inside the block | Gives back |
|---|---|---|
let | it | the block's result |
run | this | the block's result |
with | this | the block's result |
also | it | the object |
apply | this | the 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()givesunresolved 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
applywhere you meantlet—"42".apply { toInt() }hands back theString, so+ 1prints421, not43.- An
alsothe chain depends on — if removing it changes the result, it belonged in the chain. withon a nullable — it is not an extension, so there is no?.; use?.runor?.let.- Nesting two
itblocks — the inner shadows the outer, silently. - Wrapping everything —
val x = compute()beatscompute().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…