Joao Alves

Engineering Manager at Skyscanner

Kotlin standard functions — just another guide

Posted at — Mar 28, 2018

image

Hello everyone and welcome to article number 9 in the series. Today we’re going to look at something that maybe should have come up after the syntax articles or so, but better late than never.

I guess I was using some of these without really knowing or paying attention to the real difference between them but last week someone asked me the difference and I was like hmm… so here we are.

Kotlin provides a set of standard functions we can use to ease our way with the language and some of the problems we face regularly while programming. Today we’re going to look into a group of functions that seem very similar in the way of using but actually have different behaviours and therefore different goals:

let | apply | run | also | with

All these functions work in a similar way in the sense they allow you to change the scope of the current function or variable, and at the same time apply multiple operations on that same variable all in the same place. So, the important thing is to look at the argument (it) the receiver (this) and the result of the function. Let’s assume we have a SomeObject data class and the following all the examples inside the playground function:

data class SomeObject(
	var someField: String = "some default value", 
	var otherField: Int = 55
	)

class StandardFunctions {

    fun playground() {
        var someObject = SomeObject()

        val letExample = someObject?.let { ... }

        val applyExample = someObject.apply { ... }

        val runExample = someObject.run { ... }

        val alsoExample = someObject.also { ... }

        val withExample = with(someObject) { ... }
    }

    fun someOtherFunction() {
        print("random message")
    }
}

And now let’s look into each one of the standard functions in detail and understand how they work.

let

I believe most of us use let as a null checker to apply or not some operations on an object. It’s probably the most famous example so nothing new here. From the official doc:

/**  
 * Calls the specified function [block] with `this` value as its argument and returns its result.  
 */

val letExample = someObject?.let {
    print(it.someField)
    this.someOtherFunction()
    "some result"
}

In this case, by using let we only print someField if someObject is not null, otherwise, nothing inside runs.

Argument (it): The argument in the case of let refers to the object where we’re applying the function, in this case, someObject

Receiver (this): The receiver refers to the class where we’re using the function, allowing us to call someOtherFunction() using this keyword.

Result: the result of let is whatever we want. By default, the return type is Unit if we don’t specify a return statement, but in our case, we’re returning same result String. Remember that in Kotlin the return keyword is not required in certain situations, this is one of them.

apply

We can compare using apply to using a builder pattern. It allows us to apply operations on an object and return that same object. From the official doc:

/**  
 * Calls the specified function [block] with `this` value as its receiver and returns `this` value.  
 */

val applyExample = someObject.apply {
    someField = "some field"
    this.otherField = 99
    "some result"
}

Argument (it): argument is not applicable when we use the apply function.

Receiver (this): The receiver refers to the object where we’re using the function, allowing us to access otherField using this keyword.

Result: the result of apply is always the object where we’re using the function, in this case, someObject is the result. The line with "some result" string has no effect at all when using apply and Android Studio even warns that the expression is unused.

run

With run we can do the same as with apply but we can return anything we want as the result of the function rather than getting the calling object by default. From the official doc:

/**  
 * Calls the specified function [block] with `this` value as its receiver and returns its result.  
 */

val runExample = someObject.run {
    print(someField)
    this.otherField = 99
    "some result"
}

Argument (it): argument is not applicable when we use the run function.

Receiver (this): The receiver refers to the object where we’re using the function, allowing us to access otherField using this keyword.

Result: the result of run is whatever we want. Same as with let function, the default return type is Unit if we don’t specify a return statement, but in our case we’re returning same result String.

also

We can use also to apply some operation in an object and return that same object. Similar to apply in a way but you need to use the argument to access the object, as the receiver in this case refers to the class and not the calling object. From the official doc:

/**  
 * Calls the specified function [block] with `this` value as its argument and returns `this` value.  
 */

val alsoExample = someObject.also {
    print(it.someField)
    this.someOtherFunction()
    "some result"
}

Argument (it): The argument in the case of also refers to the object where we’re applying the function, in this case, someObject

Receiver (this): The receiver refers to the class where we’re using the function, allowing us to call someOtherFunction() using this keyword.

Result: the result of also is always the object where we’re using the function, in this case someObject is the result. The line with "some result" string has no effect at all when using also

with

We can use with if we want to call several different methods on an object for example as we don’t need to do it.method() all the time, we can just call the methods directly as the receiver inside the block is the object itself. And we can also return whatever we want. From the official doc:

/**  
 * Calls the specified function [block] with `this` value as its          receiver and returns `this` value.  
 */

val withExample = with(someObject) {
    print(someField)
    this.otherField = 99
    "some result"
}

Argument (it): argument is not applicable when we use the with function.

Receiver (this): The receiver refers to the object where we’re using the function, allowing us to access otherField using this keyword.

Result: the result of with is whatever we want. Same as with let and run functions, the default return type is Unit if we don’t specify a return statement, but in our case we’re returning same result String.

Table of truth

To summarise and to have everything in the same place, I believe a table it’s a nice and quick way to refer to, when we need to remember how these standard functions work.

image

And that’s it for today, I hope it’s now more clear how these standard functions work and when to use one or the other. If you enjoyed the article don’t forget to 👏 and please share your ideas and comments as usual. The code is available under the standardfunctions package on the series project. See you at number 10 👋

Kotlin backend? Yes it’s possible ⇦ PREVIOUS

NEXT ⇨ Kotlin runtime checks — require and check


comments powered by Disqus