Blog / April 29, 2021 / 4 mins read / By Suneet Agrawal

Kotlin also function

In continuation to my previous post where I explained about Kotlin let function and Kotlin apply function, let’s try to understand today about also function today.

Just to recap, Scope functions are nothing but the functions which define to the scope of the calling object. We can apply operations on that object within that scope and return the object itself from that scope function or we can even return the result of operation or operations from the scope function.

There are a few scope functions

To keep this article short and to the point, we will talk only about also in this article and all the use cases around it.

also is used to perform some actions on the object and returns the object itself. A reference to the calling object is available inside the also function instead of the context (this).

More than functional, also can be used as grammatical where we can read as “also do the following with the object”.

To understand also function lets look at the implementation of also function first.

/**
 * Calls the specified function [block] 
 * with `this` value as its argument 
 * and returns `this` value.
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

also is an extension function to Template class which takes a lambda as a parameter, apply contract on it, execute the lambda function within the scope of calling object and ultimately return the same calling object of Template class itself.

This clarifies a few things

  1. The return type of the also function is nothing but the same calling object.
  2. Since its an extension function to the Template class, it can be called on any object.

Now let’s understand what is the contract.

The contract is nothing but a contract applied to the passed lambda as a parameter.

/**
 * Specifies the contract of a function.
 *
 * The contract description must be at the beginning of a function and have at least one effect.
 *
 * Only the top-level functions can have a contract for now.
 *
 * @param builder the lambda where the contract of a function is described with the help of the [ContractBuilder] members.
 *
@ContractsDsl
@ExperimentalContracts
@InlineOnly
@SinceKotlin("1.3")
@Suppress("UNUSED_PARAMETER")
public inline fun contract(builder: ContractBuilder.() -> Unit) { }

This is exactly the same contract as to any other scope function. This superimposes some conditions on the lambda we passed as a parameter to the also function. What conditions it superimposed, we need to check the parameter of the contract

And what contract applied in the also function ?

/**
 * Specifies that the function parameter [lambda] is invoked in place.
 *
 * This contract specifies that:
 * 1. the function [lambda] can only be invoked during the call of the owner function,
 *  and it won't be invoked after that owner function call is completed;
 * 2. _(optionally)_ the function [lambda] is invoked the amount of times specified by the [kind] parameter,
 *  see the [InvocationKind] enum for possible values.
 *
 * A function declaring the `callsInPlace` effect must be _inline_.
 *
 */
/* @sample samples.contracts.callsInPlaceAtMostOnceContract
* @sample samples.contracts.callsInPlaceAtLeastOnceContract
* @sample samples.contracts.callsInPlaceExactlyOnceContract
* @sample samples.contracts.callsInPlaceUnknownContract
*/
@ContractsDsl public fun <R> callsInPlace(lambda: Function<R>, kind: InvocationKind = InvocationKind.UNKNOWN): CallsInPlace

It superimposes 2 conditions

  1. The lambda will be invoked only during owner function call and it won’t be called once the owner function is completed.
  2. The number of times this lambda function will be invoked (which is exactly once in our case) which is an enum.

The above conditions are clear from there definition itself.

So basically, also function will be

  • called only during the owner function will be called.
  • called ONLY ONCE.
  • called on the calling object.
  • and will return the object itself on which the also function is called.

Now let’s look at the use cases

also is used to perform actions on the object that take the context object as an argument.

class ConnectionManager {
    var endPoint: String = ""
    var credentials: Pair<String, String> = Pair("", "")

    fun connect() {
        //make network connection
    }
}

val connectionManager = ConnectionManager()
        .apply {
            endPoint = "http://endpoint.com"
            credentials = Pair("username", "password")
        }
        .also {
            it.connect()
        }

it can also be used for chaining as also returns this context only.

val connectionManager = ConnectionManager()
        .apply {
            endPoint = "http://endpoint.com"
            credentials = Pair("username", "password")
        }
        .also {
            it.connect()
        }
        .also {
          print("connection is made on ${it.endPoint}")
        }

we can even use a named parameter in also.

val connectionManager = ConnectionManager()
        .apply {
            endPoint = "http://endpoint.com"
            credentials = Pair("username", "password")
        }
        .also { manager ->
            manager.connect()
        }

Since there is no this context passed inside also function, it doesn’t shadow the this context from the outer scope.

Things to keep in mind about also,

  1. also uses the context as it or we can use a named parameter.
  2. also returns the calling object itself.
Comments