Hello folks, I have taken a deep dive in Kotlin lately, and I came across an intermediate level topic, “Scope functions”. Sounds scary right? It’s not, let’s find out!
What is a scope?
Basically, a scope in any programming langauge is defined as the space in which a particular object, or a particular variable is recognized, or can be accessed. There are 2 scopes at large
Global scope: A variable or object that is recognized anywhere in the program.
Local scope: A variable or object that is only recognized in a block or a function.
Scope functions in Kotlin are defined as functions, that create a local scope around the object, and provide a reference to the same object to make changes to it or execute some code.
Each scope function in kotlin, takes in a lambda expression, and either returns the object or some result of the lambda expression at the end of execution. One can reference the object in the temporary local scope with keywords like it, or this.
There are 5 different scope functions in Kotlin, let us understand each one in detail with an example.
Scope functions in Kotlin are
- let
- apply
- run
- also
- with
- Let
Let scope function is generally used to check null safety while assigning values to a variable, and then returning the object with further actions if required.
Look at the code snippet below
fun dialNumber(phoneNumber: String){
println("Dialing number: ${phoneNumber}")
}
fun getPhoneNumber(): String{
return "Some random phone number"
}
fun main(){
val phoneNumber: String? = getPhoneNumber();
dialNumber(phoneNumber);
}
Now, in the above code snippet
There are 3 function, dialNumber( ), getPhoneNumber( ) and main( ) function.
dialNumber( ) takes a String parameter and getPhoneNumber( ) returns a String value.
In the main( ) function, a nullable String variable phoneNumber stores the number from getPhoneNumber( ) function, and is passed to dialNumber( ).
Problem in the above code snippet is, that dialNumber( ) requires a String parameter, whereas we are passing a nullable String type phoneNumber to it.
Now, look at the code snippet below
fun dialNumber(phoneNumber: String){
println("Dialing number: ${phoneNumber}")
}
fun getPhoneNumber(): String{
return "Some random phone number"
}
fun main(){
val phoneNumber: String? = getPhoneNumber();
val didDial = phoneNumber?.let{
dialNumber(it)
}
}
What is happening here is
- We perform a safe call on phoneNumber using ?, followed by let.
- let, creates a temporary scope, takes a lambda function where phoneNumber can be accessed using it keyword.
- dialNumber is called in this scope with reference to phoneNumber, using it call.
- Result of the function is assigned to the didDial variable. Using let, a safe null check is performed.
Using let, a safe null check is performed.
2. Apply
apply is another scope function. Apply is mostly used to initialize data to fields of an object, or to run method functions of an object during the time of object creation.
For example, without using apply
class Student(){
val id: String?
fun calculateGrade() = println("Calculating score!")
fun markAttendance() = println("Marked attendance!")
}
fun main(){
val student: Student = Student()
student.id = "1"
student.calculateGrade()
student.markAttendance()
}
In the above code, we first need to create an instance of the student class, in order to access or modify its variables and functions.
For simpler classes, the approach feels simpler, but for complex classes with different and many properties, this process becomes cumbersome.
Using apply, we can access and modify properties of an object at the time of creation
class Student(){
val id: String?
fun calculateGrade() = println("Calculating score!")
fun markAttendance() = println("Marked attendance!")
}
fun main(){
val student: Student = Student().apply{
id = "1"
calculateGrade()
markAttendance()
}
}
3. Run
Run has a similar use case as apply, additionally one can use run to execute and compute some code while accessing properties of an object.
class Student(){
val id: String?
fun calculateGrade() = println("Calculating score!")
fun markAttendance() = println("Marked attendance!")
}
fun main(){
val student: Student = Student().apply{
id = "1"
}.run{
calculateGrade()
markAttendance()
}
}
4. Also
Also can be used to perform extra computation with an object in the local scope and then return the object for continuation of flow of code. For example: Printing logs in the terminal
class Student(){
val id: String?
fun calculateGrade() = println("Calculating score!")
fun markAttendance() = println("Marked attendance!")
}
fun main(){
val student: Student = Student().apply{
id = "1"
}.run{
calculateGrade()
markAttendance()
}.also{
println("Student initialized with id: $id");
}
}
5. with
with is not an extension function, and thus the syntax is different than other scope functions. Here the object is passed as an argument to with.
Let’s understand with an example
class Car{
fun drive(speed: Int, acceleration: Double, distance: Double){
println("Driving car at $speed with $acceleration till $distance");
}
fun getMileage(distance: Double, time: Double, fuelConsumed: Double){
println("Calculating mileage for $distance, in $time, with $fuelConsumed");
}
}
fun main(){
val userDummyTestCar1: Car = Car()
userDummyTestCar1.drive(10, 20, 15)
userDummyTestCar1.getMileage(12, 11.4, 12.5)
userDummyTestCar1.drive(21, 33.43, 18.1)
userDummyTestCar1.getMileage(22.1, 11.42, 23.54)
}
Now, in the above code base, the same user object is called redundantly. The code can be simplified here using with.
val userDummyTestCar1: Car = Car()
with(userDummyTestCar1){
drive(10, 20, 15)
getMileage(12, 11.4, 12.5)
drive(21, 33.43, 18.1)
getMileage(22.1, 11.42, 23.54)
}
Overview
Below is the overview of each scope function one can refer to.
Sources
Official kotlin docs — https://kotlinlang.org/docs/kotlin-tour-intermediate-scope-functions.html
Hope this was a good read.
See you again
Top comments (0)