Introduction
There is a lot of syntax sugar in Kotlin that allows you to make your code more readable and clear. One of the examples is scope functions. They organize code in convenient blocks and help to easily support it. In this topic, we will consider what scope functions are and how two of them, apply and also, work.
Scope functions
There are five scope functions in Kotlin: let, run, with, apply, and also. They don't perform any specific actions but just organize your code and execute certain operations in the object context. These functions create a temporary scope for objects and invoke code from lambdas. Inside a lambda, we can communicate with objects using the keywords it or this (we will consider them in the following topics).
It sounds a little abstract, yes, but let's see an example – it will be simple and cool! We have a data class Musician, which contains some information about famous people: their names, the instrument they play, and the name of their bands. As we know, after the breakup of Nirvana, Dave Grohl founded the band Foo Fighters. He was a drummer first and then began to play the guitar. Now, we need to change our object accordingly. To do that, we will use one of the scope functions – apply.
data class Musician(var name: String, var instrument: String, var band: String)
fun main() {
Musician("Dave Grohl", "Drums", "Nirvana").apply {
println(this)
band = "Foo Fighters"
instrument = "Guitar"
println(this)
}
}
// Output:
// Musician(name=Dave Grohl, instrument=Drums, band=Nirvana)
// Musician(name=Dave Grohl, instrument=Guitar, band=Foo Fighters)
Voila! Dave has successfully changed his characteristics: the band name and the musical instrument. And we got a clear and readable piece of code.
Now let's see how this code would look without the scope function.
data class Musician(var name: String, var instrument: String, var band: String)
fun main() {
val dave = Musician("Dave Grohl", "Drums", "Nirvana")
println(dave)
dave.band = "Foo Fighters"
dave.instrument = "Guitar"
println(dave)
}
We see that without apply our code became heavier and received a new variable. Moreover, in the code with apply, we have operations conveniently grouped, while without apply, all operations are located on the same level. And if we add yet more operations, the code may become unreadable.
Now, let's consider in details two scope functions – apply and also – and discuss how and where they work. You'll see that they are very similar.
apply
Here are two major features of the apply function:
-
The context object is available as
this. -
The function returns the context object.
apply is commonly used for object setting – for example, if you want to assign new values to class methods or parameters. It sounds like "Hey, do apply these settings to this object and its parameters!". Note that in this case, you need to have access to object parameters.
Do you remember Jonny Greenwood from Radiohead? Let's now enter his data!
data class Musician(var name: String, var instrument: String = "Guitar", var band: String = "Radiohead")
fun main() {
Musician("Jonny Greenwood").apply {
instrument = "Harmonica" // here we can also use this.instrument
band = "Pavement"
}
}
We've changed the object and set some of Jonny's parameters – now he can play harmonica in Pavement's album "Terror Twilight" (1999). Such a talented guy!
Note how we accessed the class parameters: we could have referred to them as this.instrument, but this can be skipped. And look how readable the code is – in its structure, we immediately see the block that applies new settings to an instance of the Musician() object.
Remember that apply returns the context object. It means that we can pass our object further down the chain and do something else with it. For example, we can copy this object with some new parameters:
fun main() {
val thom = Musician("Jonny Greenwood")
.apply {
instrument = "Harmonica"
band = "Pavement"
}.copy(name = "Thom York") // After .apply we have an instance of Musician()
}also
Here are two major characteristics of the also function:
-
The context object is available as
it. -
The function returns the context object.
The usage of also is similar to that of apply, but it's recommended to choose also when you work with the entire object and don't care about its parameters or methods. It sounds like "Hey, now do something with this object and also (in that moment and as if before the main operation) perform an additional action". For example, imagine that our old Jonny decided to learn a new instrument:
val instruments = mutableListOf("Guitar", "Harmonica", "Bass guitar")
instruments
.also { println("Right now I can play these instruments: $it") }
.add("Theremin")
We declare a variable, pass some value to it, and at the same time invoke the println() function using the also function.
Meanwhile, also has an interesting feature – it seems like it executes operations immediately (actually, it returns the context before operations are executed). See how the trick works:
var a = 10
var b = 5
a = b.also { b = a }
println("a = $a, b = $b") // Output: a = 5, b = 10
Great!
Conclusion
We've learned five scope functions and taken a closer look at two of them – also and apply. Here is a little summary:
-
There are five scope functions in Kotlin, which help us to organize our code and do some job with objects.
-
We have to use
applyto set object parameters. -
We have to use
alsoto do some additional things with objects.
In the next topic, we will consider the usage of with, run, and let – three functions that return lambda's results.