Kotlin Scope Functions - mariamaged/Java-Android-Kotlin GitHub Wiki
Kotlin - Scope Functions
- Kotlin has
six global functions
that allow you to create alamda expression
todeclare a limited scope inside of a function
.
let()
- The let() can be called from any object.
- It takes a lamda expression.
- And it passes whatever
let
was called on into the lamda expression as a parameter.- We can refer to that parameter as
it
. - Or explicitly name it.
- We can refer to that parameter as
one.let { value -> println(value.dec() }
Dealing with Nullable Types
// First Scenario
val one = 1
one.let { println(it.dec()) }
// Second Scenario
val mayBeZero: Int? = null
mayBeZero?.let { println(it.dec() }
- In the second scenario, we are using a safe call ?.
- let() is only called when
mayBeZero
isnot null
. - Otherwise, the let() call is skipped.
- let() is only called when
- As a result,
it
is anInt
, not anInt?
, because theKotlin compiler
knows by definition the parameter passed to the lamda expression cannot benull
. - This is great for cases where you have a bunch of work that you want to perform on a value if it is not null, and you are happy to skip over that work when it is null.
- The
let() function
returns whatever the last statement is inside the lamda expression, andwe can use that to populate a variable, as a parameter to a function, or whatever
.
val one = 1
println(one.let { it.dec() })
val mayBeZero: Int? = null
println(mayBeZero?.let{ it.dec() })
0
null
- So, in the mayBeZero scenario, ?. sees that mayBeZero is null and skips the let() call, returning null, and so we print null to the output.
apply()
- Many programmers have run into cases where they had to repeat a reference to a variable or something a lot.
- You have some object and you need to call a
whole bunch of methods
on it to configure it, such as setting the individual fields of a Calender object.
import java.util.Calender
val sometime = Calender.getInstance()
sometime.set(Calender.YEAR, 1980)
sometime.set(Calender.MONTH, 1)
sometime.set(Calender.DAY_OF_MONTH, 22)
sometime.set(Calender.HOUR_OF_DAY, 17)
sometime.set(Calender.MINUTE, 0)
sometime.set(Calender.SECOND, 0)
sometime.set(Calender.MILLISECOND, 0)
println(sometime)
- We could simplify this with let:
import java.util.Calender
val sometime = Calender.getInstance()
sometime.let {
it.set(Calender.YEAR, 1980)
it.set(Calender.MONTH, 1)
it.set(Calender.DAY_OF_MONTH, 22)
it.set(Calender.HOUR_OF_DAY, 17)
it.set(Calender.MINUTE, 0)
it.set(Calender.SECOND, 0)
it.set(Calender.MILLISECOND, 0)
}
println(sometime)
- However, apply works even better for this scenario.
- You call it on some object, supplying a lamda expression.
- The object becomes this inside of the lamda expression, allowing you to just call functions on it without having to name it.
- Alas,
Kotlin/JS
does not have java.util.Calender, so we cannot use that. but we can fake it.
class Calender {
private val pieces = mutableMapOf<String, Int>()
fun set(key: String, value: Int) {
pieces[key] = value
}
override fun toString() : String = pieces.toString()
}
fun main() {
val sometime = Calender()
sometime.apply {
set("YEAR", 1980)
set("MONTH", 1)
set("DAY_OF_MONTH", 22)
set("HOUR_OF_DAY", 17)
set("MINUTE", 0)
set("SECOND", 0)
set("MILLISECOND", 0)
}
println(sometime)
}
- Also, apply() returns whatever object that you called it on, so we can further simplify this.
- This is the same as the previous example, except that we are just chaining
apply() right onto the call to getInstance()
, rather than bothering to reference sometime to do it.
class Calender {
private val pieces = mutableMapOf<String, Int>()
fun set(key: String, value: Int) {
pieces[key] = value
}
override fun toString() : String = pieces.toString()
}
fun main() {
val sometime = Calender().apply {
set("YEAR", 1980)
set("MONTH", 1)
set("DAY_OF_MONTH", 22)
set("HOUR_OF_DAY", 17)
set("MINUTE", 0)
set("SECOND", 0)
set("MILLISECOND", 0)
}
println(sometime)
}
- We could eliminate sometime:
class Calender {
private val pieces = mutableMapOf<String, Int>()
fun set(key: String, value: Int) {
pieces[key] = value
}
override fun toString() : String = pieces.toString()
}
fun main() {
println(Calender().apply {
set("YEAR", 1980)
set("MONTH", 1)
set("DAY_OF_MONTH", 22)
set("HOUR_OF_DAY", 17)
set("MINUTE", 0)
set("SECOND", 0)
set("MILLISECOND", 0)
})
}
run()
run()
works likeapply()
in that whatever you call run() on becomes this in the scope of the lamda expression.run
works likelet()
in that the result of run is whatever the last statement of the lamda expression returns.
run() is useful when you want to call a bunch of functions on some object, but then the end result is not the object itself, but something else.
class Calender {
private val pieces = mutableMapOf<String, Int>()
fun set(key: String, value: Int) {
pieces[key] = value
}
override fun toString() : String = pieces.toString()
}
fun main() {
val sometime = Calender().run {
set("YEAR", 1980)
set("MONTH", 1)
set("DAY_OF_MONTH", 22)
set("HOUR_OF_DAY", 17)
set("MINUTE", 0)
set("SECOND", 0)
set("MILLISECOND", 0)
toString()
}
println(sometime)
}
- In the context of println(), there is no practical difference, as println() will call toString() to generate what to print.
- However, in other situations, the value that you generate from the run lamda expression might be more distinctly different.