Scope Functions

As we saw in an earlier chapter, “scope” refers to where variables can be referenced. For example, a function creates a scope; variables declared in that function are not accessible outside of that function.

Similarly, variables declared inside of a lambda expression are not accessible outside of that lambda expression. However, such lambda expressions need to be applied to something, such as being used by forEach().

Kotlin has six global functions that allow you to create a lambda expression to declare a limited scope inside of a function.

That may sound both esoteric (why would one care?) and repetitious (do we really need six of these? can’t we use just one?).

In truth, these “scope functions” get used a lot in common Kotlin development, so it is important to understand what they are and how they get used.

let()

In the preceding chapter, we saw lots of ways of dealing with values that might potentially be null, because they are referenced by variables or properties with nullable types.

Another popular way of dealing with nullable types is to use the first of the scope functions: let():

  val one = 1

  one.let { println(it.dec()) }

  val maybeZero : Int? = null

  maybeZero?.let { println(it.dec()) }

The let() function can be called on any object. It takes a lambda expression, and it passes whatever let() was called on into the lambda expression as a parameter. We can refer to that parameter as it, as in the above examples, or explicitly name it (e.g., one.let { value -> println(value.dec()) }).

This may seem silly. And, in the case of the first let(), where we are calling it on one, it probably is silly.

The second scenario, though, is useful. Because 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 that by definition the parameter passed to the lambda expression cannot be null — otherwise, we would have not called let() in the first place. So while maybeZero has to be referenced using safe calls and related techniques, code inside the lambda expression does not have to worry about that. In effect, we have “de-null-ed” the value. But, in our case, maybeZero is null, so that let() is skipped, and we only print 0 for the first let().

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 lambda 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() })

Here, we only do the dec() call inside of the lambda expression, and we print whatever let() returns. This results in:

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.

In summary, let():


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.