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
- The return type of the
also
function is nothing but the same calling object. - 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
- The lambda will be invoked only during owner function call and it won’t be called once the owner function is completed.
- 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
,
also
uses the context asit
or we can use a named parameter.also
returns the calling object itself.