DEV Community

Cover image for Scope Functions in Kotlin
Prithviraj Kapil
Prithviraj Kapil

Posted on • Originally published at Medium

Scope Functions in Kotlin

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

  1. Global scope: A variable or object that is recognized anywhere in the program.

  2. 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

  1. let
  2. apply
  3. run
  4. also
  5. with

  1. 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);
}
Enter fullscreen mode Exit fullscreen mode

Now, in the above code snippet

  1. There are 3 function, dialNumber( ), getPhoneNumber( ) and main( ) function.

  2. dialNumber( ) takes a String parameter and getPhoneNumber( ) returns a String value.

  3. 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)
  }
}
Enter fullscreen mode Exit fullscreen mode

What is happening here is

  1. We perform a safe call on phoneNumber using ?, followed by let.
  2. let, creates a temporary scope, takes a lambda function where phoneNumber can be accessed using it keyword.
  3. dialNumber is called in this scope with reference to phoneNumber, using it call.
  4. 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()
}
Enter fullscreen mode Exit fullscreen mode

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()
  }
}
Enter fullscreen mode Exit fullscreen mode

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()
  }
}
Enter fullscreen mode Exit fullscreen mode

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");
  }
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

Overview
Below is the overview of each scope function one can refer to.

Image description


Sources

Official kotlin docs — https://kotlinlang.org/docs/kotlin-tour-intermediate-scope-functions.html

Hope this was a good read.

See you again

Image description

Top comments (0)