Scope functions
In Kotlin, scope functions allow you to execute a function, i.e. a block of code, in the context of an object. The object is then accessible in that temporary scope without using the name. Although whatever you do with scope functions can be done without, they enable you to structure your code differently. Using them can increase readability and make your code more concise.
The Kotlin standard library offers four different types of scope functions which can be categorized by the way they refer to the context object and the value they return. A scope function either refers to the context object as a function argument or a function receiver. The return value of a scope function is either the function result or the context object.
The available functions are let
, also
, apply
, run
, and with
. The following table summarizes the characteristics of each function based on the way the context object can be accessed and the return type as described above:
Context Object As Function Argument | Context Object As Function Receiver | |
Returns: Function Result | let | run , with |
Returns: Context Object | also | apply |
The difference between run
and with
lies only in the way they are called. While all other scope functions are implemented as extension functions, with
is a regular function.
Now that I’ve mentioned concepts such as function receivers and extension functions it makes sense to briefly explain them before we move on into the detailed descriptions of the scope functions. If you are already familiar with function receivers and extension functions in Kotlin you can skip the next section.
Function arguments, extension functions, receivers
Kotlin allows for treating functions as values. This means you can pass functions as arguments to other functions. Using the ::
operator you can convert a method to a function value. To increase readability, the last function argument can be placed outside of the argument list.
The following example illustrates how to do that by defining a higher order function combine
, which takes a function argument f
. We’re invoking it with the plus
method from the Int
class and with an anonymous function literal both within the and outside of the argument list:
1// Apply function argument f to integers a and b
2fun combine(a: Int, b: Int, f: (Int, Int) -> Int): Int = f(a, b)
3
4// Using the plus method as a function value
5combine(1, 2, Int::plus)
6
7// Passing a function literal
8combine(1, 2, { a, b ->
9 val x = a + b
10 x + 100
11})
12
13// Passing it outside of the argument list
14combine(1, 2) { a, b ->
15 val x = a + b
16 x + 100
17}
Extension functions are a way to extend existing classes or interfaces you do not necessarily have under your control. Defining an extension function on a class lets you call this method on instances of that class as if it was part of the original class definition.
The following example defines an extension function on Int
to return the absolute value:
1fun Int.abs() = if (this < 0) -this else this
2
3(-5).abs() // 5
Function literals with receiver are similar to extension functions as the receiver object is accessible within the function through this
. The following code snippet defines the extension function from before but this time as a function literal with receiver:
1val abs: Int.() -> Int = { if (this < 0) -this else this } 2 3(-5).abs() // 5
A common use case for function literals with receivers are type-safe builders . Now that we have covered the basics let’s look at the five scope functions individually.
Let, also, apply, run, with
Let
The let
scope function makes the context object available as a function argument and returns the function result. A typical use case is applying null-safe transformations to values.
1val x: Int? = null 2 3// null-safe transformation without let 4val y1 = if (x != null) x + 1 else null 5val y2 = if (y1 != null) y1 / 2 else null 6 7// null-safe transformation with let 8val z1 = x?.let { it + 1 } 9val z2 = z1?.let { it / 2 }
Also
The apply
scope function makes the context object available as a function argument and returns the context object. This can be used when you are computing a return value inside a function and then want to apply some side effect to it before you return it.
1// assign, print, return
2fun computeNormal(): String {
3 val result = "result"
4 println(result)
5 return result
6}
7
8// return and also print
9fun computeAlso(): String =
10 "result".also(::println)
Apply
The apply
scope function makes the context object available as a receiver and returns the context object. This makes it very useful for “ad-hoc builders” of mutable objects, such as Java Beans.
1// Java Bean representing a person
2public class PersonBean {
3 private String firstName;
4 private String lastName;
5 public void setFirstName(String firstName) {
6 this.firstName = firstName;
7 }
8 public String getFirstName() {
9 return firstName;
10 }
11 public void setLastName(String lastName) {
12 this.lastName = lastName;
13 }
14 public String getLastName() {
15 return lastName;
16 }
17}
1// Initialization the traditional way 2val p1 = PersonBean() 3p1.firstName = "Frank" 4p1.lastName = "Rosner" 5 6// Initialization using apply 7val p2 = PersonBean().apply { 8 firstName = "Frank" 9 lastName = "Rosner" 10}
Run and with
The run
scope function makes the context object available as a receiver and returns the function result. It can be used with or without a receiver. When using it without a receiver you can compute an expression using locally scoped variables. By using a receiver, run
can be called on any object, e.g. a connection object.
1// compute result as block result 2val result = run { 3 val x = 5 4 val y = x + 3 5 y - 4 6} 7 8// compute result with receiver 9val result2 = "text".run { 10 val tail = substring(1) 11 tail.toUpperCase() 12}
The with
function works exactly as run
but is implemented as a regular function and not an extension function.
1val result3 = with("text") {
2 val tail = substring(1)
3 tail.toUpperCase()
4}
Summary
In this post we learned about the scope functions let
, also
, apply
, run
, and with
. They differ in the way they refer to the context object and the value they return. Combined with the concepts of function arguments, extension functions and receivers, scope functions are a useful tool to produce more readable code.
What do you think about scope functions? Have you ever used them in one of your projects? Can you remember which one to use when? Let me know your thoughts in the comments!
References
More articles
fromFrank Rosner
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog author
Frank Rosner
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.