Kotlin — Scope Functions
In this article, I’m going to examine the scope functions that make developers’ life a little easier. First of all, I would like to make a simple description of scope functions.
What are the Scope Functions?
These are five of them: let
, run
, with
, apply
and also
. The main purpose of behind the scope functions is to execute a block of code within the context of an object. They all take almost the same task, but with minor nuances. First I’m going to try to define the distinctions between each of them below.
What is the main distinctions between each of them?
There are two main differences between each scope function:
- The first one is the way to refer to the context object
— There are two ways to access the context object. One of them is a lambda receiver (this
) and the another one is a lambda argument(it
).When we call the such functions, we can access the object without its name by using one of them. In short, if we want to group these scope functions according to ways of access the context object, we can group them as follows.
run
,with
andapply
can access inside of an object’s scope by usingthis
.let
andalso
can access inside of an object’s scope by usingit
.
2. The second one is return value of scope function
— Each scope function has a return value.
apply
andalso
return the context object.let
,run
andwith
return the lambda result.
Let’s examine each function below.
let
When we are using let
in code, we can access inside of an object’s scope by using it
and the return value is the lambda result . Here is some code:
val person = Person("Zed", 50)
val result = person.let { it.age * 5 }
println(person)
println(result)
Output is:
Person(name=Zed, age=50)
250
As you can see the result is the lambda result. We can also use let
function one after another like chain.Here is a sample of that usage:
val person = Person("Zed", 50)
val result = person.let {it.name.length}
.let { it * 2 }
.let{it - 1}
println(result)
Output is:
5
Don’t forget each let
function return lambda result.We can write the same code using a single let
function.
val person = Person("Zed", 50)
val result = person.let {
val length = it.name.length
val multipliedByTwo = length * 2
multipliedByTwo - 1}
println(result)
Output is:
5
We can also use let
to do a null check most of time.
var str: String? = null
var result = str?.let { it.length } ?: -1
println(result)
Output is:
-1
By the way, we can use the method reference (::
) instead of lambda like that:
val person = Person("Zed", 50)
person.let {it.name.length}
.let(::println)
Output is:
3
also
When we are using also
in code, we can access inside of an object’s scope by using it
and the return value is the object itself unlike the let
. We can use also
for additional actions that don’t modify the object, such as logging. Here is a simple sample:
var list = mutableListOf("one", "two", "three")list.also{
println("The list size before adding new one: ${it.size}")
}.add("four")println("The list size after adding new one: ${list.size}")
Output is:
The list size before adding new one: 3
The list size before adding new one: 4
We couldn’t write the same code with let
. Because let
return the lambda result, not the list object. So we couldn’t add a new item into the list after using let
.
with
When we are using with
in code, we can access inside of an object’s scope by using this
and the return value is the lambda result . This function can take one argument unlike others because it’s a non-extension function. Here is a simple sample that shows the usage:
var person = Person("Zed", 50)
println(person)
with(person){
name = "No Name"
this.age= 26 //'this' is optional
}
println(person)
Output is:
Person(name=Zed, age=50)
Person(name=No Name, age=26)
This function can be useful when you want to modify object or call object methods.
run
When we are using run
in code, we can access inside of an object’s scope by using this
and the return value is the lambda result . run
does the same as with
but invokes as let
. Here is a simple sample:
val person = Person("Zed", 50)
val result = person.run {
val length = name.length
val multipliedByTwo = length * 2
multipliedByTwo - 1}
println(result)
The difference of the run
function from the let
function is that it doesn’t need the keyword it
when accessing the object.
apply
When we are using apply
in code, we can access inside of an object’s scope by using this
and the return value is the object itself . apply
does the same as also
but the difference of the apply
function from the also
function is that it doesn’t need the keyword it
when accessing the object. Here is a simple sample:
var list = mutableListOf("one", "two", "three")list.apply{
println("The list size before adding new one: ${size}")}.add("four")}.add("four")
println("The list size after adding new one: ${list.size}")
Output is:
The list size before adding new one: 3
The list size after adding new one: 4
Conclusion
To sum up, in this article I tried to simplify the outline of the scope functions which are complicated at the first sight in the official document. I hope it was useful for you :)