Kotlin Scope Functions - mariamaged/Java-Android-Kotlin GitHub Wiki

Kotlin - Scope Functions

  • Kotlin has six global functions that allow you to create a lamda expression to declare 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.
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 is not null.
    • Otherwise, the let() call is skipped.
  • As a result, it is an Int, not an Int?, because the Kotlin compiler knows by definition the parameter passed to the lamda expression cannot be null.
  • 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, and we 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 like apply() in that whatever you call run() on becomes this in the scope of the lamda expression.
  • run works like let() 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.