Blog / May 4, 2021 / 5 mins read / By Suneet Agrawal

Kotlin with function

Kotlin has made our life very easy by providing features like extension functions, nullability check and much more. One such kind of really helpful feature is Scope functions. Once you understand what scope functions are, you will not able to resist yourself from using them.

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 with in this article and all the use cases around it.

with is not an extension function to Template class but a normal extension function that takes a Template class object and a higher-order function as parameters, applies the operations to the passed Template class object and return the lambda expression or results. The return type can also be void.

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

Why with is not an extension function to Template class?

with is not an extension function to Template class because we don’t want to use it for chaining purpose. For chaining purpose, we have let, also and apply scope functions. with can only be the starting point of any expression. That’s the reason it’s not an extension function to the Template class.

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

/**
 * Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

This clarifies a few things

  1. The return type of the with function is nothing but the last expression we returned from our passed lambda parameter.
  2. Although it’s not an extension function to Template class but we can pass any object as the first param to with function.

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 with function. What conditions it superimposed, we need to check the parameter of the contract

And what contract applied in the with 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, with function will be

  • called only during the owner function will be called.
  • called ONLY ONCE.
  • called on the calling object.
  • and will return the expression returned by the lambda passed to the with function.

Now let’s look at the use cases

with is used to call the function on the context object without providing the lambda result.

This means with can’t be chained or we can’t pass any lambda result to with function. with will always be the starting point of any expression.

class Employee {
    var firstName: String = ""
    var age: Int = 0
}

val employee: Employee = Employee()

with(employee){
    firstName = "Suneet"
    age = 27

    println(firstName)
}

Please note that we can even point to the calling object by this but we can’t use a named parameter in apply.

val employee: Employee = Employee()

with(employee){
    this.firstName = "Suneet"
    this.age = 27

    println(this.firstName)
}

We can avoid the use of this pointer or use it to avoid the conflicts between other properties or objects with the same name within that class. It points to the calling object only.

we can return the last expression from the higher-order function passed to the with function.

val details = with(employee){
    this.firstName = "Suneet"
    this.age = 27

    "The name of the employee is $firstName and age is $age"
}

println(details)

We can even chain the with function

val list = listOf(1, 2, 3, 4, 5)
with(list){
    list.filter { it % 2 == 0 }
}
.forEach { 
    println(it) 
}

Things to keep in mind about with,

  • with is not an extension function to Template class.
  • with uses the context as this and we can not use a named parameter instead of this.
  • with return the last expression of the lambda passed which can also be void.
Comments