Open In App

Kotlin – Scope Function

Last Updated : 15 Feb, 2021
Improve
Improve
Like Article
Like
Save
Share
Report

There are several functions in the Kotlin standard library that help in the execution of a block of code within the context of an object. Calling these functions on an object with lambda expression creates a temporary scope. These functions are called Scope Functions. We can access the object of these functions without its name. Sounds confusing! Let’s see an example,

Example: Without using scope function

Kotlin




class Company() {
    lateinit var name: String
    lateinit var objective: String
    lateinit var founder: String
}
  
fun main() {
    // without using scope function
  
    // creating instance of Company Class
    val gfg = Company() 
  
    // initializing members of the class
    gfg.name = "GeeksforGeeks"
    gfg.objective = "A computer science portal for Geeks"
    gfg.founder = "Sandeep Jain"
  
    println(gfg.name)
}


Output:

GeeksforGeeks

Example: Using scope function

Kotlin




class Company() {
    lateinit var name: String
    lateinit var objective: String
    lateinit var founder: String
}
  
fun main() {
    // using scope function
    val gfg = Company().apply {
        // don't need to use object
        // name to refer members
        name = "GeeksforGeeks"
        objective = "A computer science portal for Geeks"
        founder = "Sandeep Jain"
    }
    println(gfg.name)
}


Output:

GeeksforGeeks

Explanation

You must have noticed that when we are not using the scope function, we need to write the object name every time to refer to members of the class. While using the scope function, we can directly refer to members without the object name. This is one of the ways of using the scope function. We will learn more about them in this article.

Scope Functions

Every scope function has well-defined use cases, although all of them have nearly the same outcome. Now let’s look at each scope functions and its use cases:

Application of using scope functions

Scope functions make code more clear, readable, and concise which are Kotlin language’s main features.

Types of scope functions

There are five types of scope functions:

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

Each of these functions is quite similar in nature with minor differences. It’s often confusing to decide which function to use and when. So, we need to know what are the differences between these functions and their use cases.

Differences in these functions:

There are mainly two differences among these functions:

  1. Way of referring to a context object (i.e. using either ‘this’ or ‘it’ keyword)
  2. return value (i.e. returns either ‘context object’ or ‘lambda result’)

Note: Context object refers to the object on which we are using the scope functions. As in our previous example – ‘gfg’ is our context object

Scope functions table:

Function

Object Reference

Return Value

let

it

Lambda result

run

this

Lambda result

with

this

Lambda result

apply

this

Context object

also

it

Context object

1. let function

Context object  :   it 
Return value    :   lambda result

Use Case:

let function is often used to provide null safety calls. Use safe call operator(?.) with ‘let’ for null safety. It executes the block only with the non-null value.

Example:

Kotlin




fun main() {
    // nullable variable a
    // with value as null
    var a: Int? = null
    // using let function
    a?.let {
        // statement(s) will
        // not execute as a is null
        print(it)
    }
    // re-initializing value of a to 2
    a = 2
    a?.let {
        // statement(s) will execute
        // as a is not null
        print(a)
    }
}


Output:

2

Explanation:

As you see when the value of ‘a’ is ‘null’ let function simply avoid the code block. Hence, solving the biggest nightmare of programmers – NullPointerException.

2. apply function

Context object   :    this
Return value     :    context object

Use Case:

As the name implies – “Apply these to the object”. It can be used to operate on members of the receiver object mostly to initialize members.

Example:

Kotlin




class Company() {
    lateinit var name: String
    lateinit var objective: String
    lateinit var founder: String
}
  
fun main() {
    Company().apply {
        // same as founder = “Sandeep Jain”
        this.founder = "Sandeep Jain" 
        name = "GeeksforGeeks"
        objective = "A computer science portal for Geeks"
    }
}


3. with function

Context object  :   this
Return value    :   lambda result

Use Case:

Recommended use of ‘with’ for calling functions on context objects without providing the lambda result.

Example:

Kotlin




class Company() {
    lateinit var name: String
    lateinit var objective: String
    lateinit var founder: String
}
  
fun main() {
    val gfg = Company().apply {
        name = "GeeksforGeeks"
        objective = "A computer science portal for Geeks"
        founder = "Sandeep Jain"
    }
  
    // with function
    with(gfg) {
        // similar to println( "${this.name}" )
        println(" $name ")
    }
}


Output:

GeeksforGeeks

4. run function

Context object   :    this
Return value     :    lambda result

‘run’ function can be said as the combination of ‘let’ and ‘with’ functions.

Use Case:

Used when the object lambda contains both initialization and the computation of the return value.  Using run we can perform null safety calls as well as other computations.

Example:

Kotlin




class Company() {
    lateinit var name: String
    lateinit var objective: String
    lateinit var founder: String
}
  
fun main(args: Array<String>) {
    println("Company Name : ")
    var company: Company? = null
    // body only executes if 
    // company is non-null
    company?.run {     
        print(name)
    }
    print("Company Name : ")
    // re-initialize company
    company = Company().apply {
        name = "GeeksforGeeks"
        founder = "Sandeep Jain"
        objective = "A computer science portal for Geeks"
    }
    // body executes as 
    // 'company' is non-null
    company?.run {     
        print(name)
    }
}


Output:

Company Name : 

Company Name : GeeksforGeeks

Explanation:

When the ‘company’ value is null, the body of the run is simply ignored. When it is non-null, the body executes.

5. also function

Context object   :    it
Return value     :    context object

Use Case:

It is used where we have to perform additional operations when we have initialized the object members.

Example:

Kotlin




fun main() {
    // initialized
    val list = mutableListOf<Int>(1, 2, 3)
  
    // later if we want to perform 
    // multiple operations on this list
    list.also {
        it.add(4)
        it.remove(2)
        // more operations if needed
    }
    println(list)
}


Output:

[1, 3, 4]

Object References

There are two ways of object referencing in scope functions:

1. this

We can refer to the context object by a lambda receiver keyword – this. this keyword does object reference in ‘run’, ‘with’, and ‘apply’ functions.

Example:

Kotlin




Company().apply {
   // same as : name = "GeeksforGeeks"
   this.name = "GeeksforGeeks"   
   this.founder = "Sandeep Jain"
   this.objective = "A computer science portal for Geeks" 
}


Note: We can exclude this keyword to refer to members of the class.

2. it

‘let’ and ‘also’ functions refer to the object’s context as a lambda argument.

Example:

Kotlin




Company().let {
   it.name = "GeeksforGeeks"
   it.founder = "Sandeep Jain"
   it.objective = "A computer science portal for Geeks"
}


Return values

There are two types of return values that a scope functions can return:

1. Lambda result

If we write any expression at the end of the code block, it becomes the return value for the scope function. Return value for ‘let’, ‘run’, and ‘with’ functions is the lambda result.

Example:

Kotlin




class Company {
    var name: String = "GeeksforGeeks"
    var founder: String = "Sandeep Jain"
    var objective: String = "A computer science portal for Geeks"
}
  
fun main() {
    val founderName: String = with(Company()) {
        // 'founder' is returned by 'with' function
        founder    
    }
    println("GfG's Founder : $founderName")
}


Output:

GfG's Founder : Sandeep Jain

2. Context object

‘apply’ and ‘also’ functions return the context object itself. In this case, we don’t need to specify the return value. The context object is automatically returned.

Example:

Kotlin




class Company {
    var name: String = "GeeksforGeeks"
    var founder: String = "Sandeep Jain"
    var objective: String = "A computer science portal for Geeks"
}
  
fun main() {
    val gfg = Company().apply {
        // any statement(s)
    }
    // gfg is an object of class Company as
    // return of apply() is context object
    print("GfG's Founder : ${gfg.founder}");
}


Output:

GfG's Founder : Sandeep Jain

Summary

  • Scope functions make code more readable, clear and concise.
  • Object reference – ‘this’ and ‘it’.
  • Return value – context object and lambda result.
  • let : working with nullable objects to avoid NullPointerException.
  • apply : changing object configuration.
  • run: operate on nullable object, executing lambda expressions.
  • also : adding additional operations.
  • with : operating on non-null objects.


Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads