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

let scope function is used to apply operations on an object and finally return the lambda expression from that scope function. The return type can also be void.

To understand let function lets look at the implementation of let function first.
  
/**
* Calls the specified function [block]
* with `this` value as its argument
* and returns its result.
*
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}

let is an extension function to Template class which takes a lambda as a parameter, apply contract on it and ultimately return the execution of the lambda we passed as a parameter to it.

This clarifies a few things

  1. The return type of the let function is nothing but the last expression we returned from our passed lambda parameter.
  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 let function. What conditions it superimposed, we need to check the parameter of the contract

And what contract applied in the let 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 callsInPlace(lambda: Function, 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, let 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 let function.

Now let's look at the use cases

The best use case of this let function is to avoid the null check. let function used with ?. ensures the execution only if the expression is non-null. You can read about the null safety in the Kotlin here.
  
class Employee {
var firstName: String? = null
var age: Int = 0
}

val employee: Employee? = Employee()
employee?.firstName = "Suneet"
employee?.age = 27

employee?.let {
println(it.age)
}

Please note that we can point to the calling object by using it
we can even use a local parameter for the calling object to avoid conflicts in the nested let or lambda function.
  
employee?.let { person ->
person.firstName?.let { name ->
println(name)
}
}

Here the person and name are the named variable within the let function scope.

If the let lambda contains only a single expression, we can even use method reference (::) instead of lambda
  
employee?.firstName?.let (::println)
Also, the let function can be used to invoke one or more functions on the result of the chain.
  
var list = mutableListOf(6, 1, 5, 2, 4, 3)
list.filter { it % 2 == 0 }.sortedBy { it }.let {
println("Sorted even numbers are : $it")
}
//this will print
Sorted even numbers are : [2, 4, 6]

If we want to return that object from let function, return without marking any return statement. In the below example, the sorted even numbers list will be returned.
  
var list = mutableListOf(6, 1, 5, 2, 4, 3)
var sortedEvenList = list.filter { it % 2 == 0 }.sortedBy { it }.let {
println("Sorted even numbers are : $it")
it
}

Things to keep in mind about let,

  1. let uses the context as it or we can use a named parameter.
  2. let return the last expression of the lambda passed which can also be void.


Reference: Kotlin docs