Understanding the need for Kotlin let - kotlin

I'm trying to understand why let is needed. In the example below I have a class Test with a function giveMeFive:
public class Test() {
fun giveMeFive(): Int {
return 5
}
}
Given the following code:
var test: Test? = Test()
var x: Int? = test?.giveMeFive()
test = null
x = test?.giveMeFive()
x = test?.let {it.giveMeFive()}
x gets set to 5, then after test is set to null, calling either of the following statements return null for x. Given that calling a method on a null reference skips the call and sets x to null, why would I ever need to use let? Are some cases where just ?. won't work and let is required?
Further, if the function being called doesn't return anything, then ?. will skip the call and I don't need ?.let there either.

let()
fun <T, R> T.let(f: (T) -> R): R = f(this)
let() is a scoping function: use it whenever you want to define a variable for a specific scope of your code but not beyond. It’s extremely useful to keep your code nicely self-contained so that you don’t have variables “leaking out”: being accessible past the point where they should be.
DbConnection.getConnection().let { connection ->
}
// connection is no longer visible here
let() can also be used as an alternative to testing against null:
val map : Map<String, Config> = ...
val config = map[key]
// config is a "Config?"
config?.let {
// This whole block will not be executed if "config" is null.
// Additionally, "it" has now been cast to a "Config" (no
question mark)
}

You need to use let if you want to chain function calls that aren't defined on the type you are chaining from.
Let's say the definition of your function was this instead:
// Not defined inside the Test class
fun giveMeFive(t: Test) {
return 5
}
Now, if you have a nullable Test and want to call this function in a chain, you have to use let:
val x = test?.let { giveMeFive(it) }

The .let{} extension function in Kotlin:
Takes the object reference as the parameter on which .let{} is called.
Returns value of any non-primitive data-type which has been returned from with let{} function. By default, it returns undefined value of kotlin.Any class.
Declaration in package kotlin:
public inline fun <T, R> T.let(block: (T) -> R): R {
return block(this)
}
Simple practical demonstration to see how .let{} extension function works in Kotlin.
Sample Code 1:-
val builder = StringBuilder("Hello ")
println("Print 0: $builder")
val returnVal = builder.let { arg ->
arg.append("World")
println("Print 1: $arg")
"Done" // Returnning some string
}
println("Print 2: $builder")
println("Print 3: $returnVal")
Sample Code 1 Output:-
Print 0: Hello
Print 1: Hello World
Print 2: Hello World
Print 3: Done
In Sample Code 1:
We created the final object of type StringBuilder with initialization value "Hello ".
In builder.let{}, the builder object reference will be passed to arg.
Here, the output Print 2: Hello World and Print 3: Hello World means that the builder and arg, both are pointing to the same StringBuilder object-reference. That's why they both print the same String value.
In the last line of .let{} function block, we are returning "Done" String value which will be assigned to returnVal.
*Hence, we get Print 3: Done output from returnVal.
Sample Code 2:-
val builder = StringBuilder("Hello ")
println("Print 0: $builder")
val returnVal = builder.let { arg ->
arg.append("World")
println("Print 1: $arg")
arg.length
}
println("Print 2: $builder")
println("Print 3: $returnVal") // Now return val is int = length of string.
Sample Code 2 Output:-
Print 0: Hello
Print 1: Hello World
Print 2: Hello World
Print 3: 11
In Sample Code 2:
What's difference:
In the last line of .let{} function block, we are returning the Int value equals to the length of StringBuilder object arg whose value at this line of code is "Hello World". So length = 11 of type Int will be assigned to returnVal.
*Hence, we get Print 3: 11 as output from returnVal.
Also try these:-
.run() {}
.apply() {}
with(obj) {}
.also() {}
Happy Coding...

fun <T, R> T.let(f: (T) -> R): R = f(this)
.let block does not equal to in multiThreading
val x? = null
if(x == null) {
}
and
x?.let{
}
.let block is thread-safe

Related

Understanding a lambda construct that contains dot followed by brackets

This is the function declaration for rememberCoilPainter:
#Composable
fun rememberCoilPainter(
request: Any?,
imageLoader: ImageLoader = CoilPainterDefaults.defaultImageLoader(),
shouldRefetchOnSizeChange: ShouldRefetchOnSizeChange = ShouldRefetchOnSizeChange { _, _ -> false },
requestBuilder: (ImageRequest.Builder.(size: IntSize) -> ImageRequest.Builder)? = null,
fadeIn: Boolean = false,
fadeInDurationMs: Int = LoadPainterDefaults.FadeInTransitionDuration,
#DrawableRes previewPlaceholder: Int = 0,
): LoadPainter<Any> {
}
The line of code I am having difficulty understanding is:
requestBuilder: (ImageRequest.Builder.(size: IntSize) -> ImageRequest.Builder)? = null
A dot appears after Builder followed by (size: IntSize)
This is the first time I've seen this construct in Kotlin and am not sure how to interpret it. This is a lambda. Normally the dot after an object refers to a sub component of a class or a package. But the ( ) after the dot isn't clear.
How do I implement the requestBuilder parameter?
This is a function with receiver type as described here: https://kotlinlang.org/docs/lambdas.html#function-types
Function types can optionally have an additional receiver type, which is specified before a dot in the notation: the type A.(B) -> C represents functions that can be called on a receiver object of A with a parameter of B and return a value of C. Function literals with receiver are often used along with these types.
It could be tricky to understand at first, but this is like you are providing a function/lambda that is a method of ImageRequest.Builder. Or in other words: your lambda receives one additional parameter of type ImageRequest.Builder and it is available in the lambda as this.
You can provide requestBuilder as any other lambda, but note that inside it you will have access to properties and methods of ImageRequest.Builder object that was provided to you.
What you are looking at is a "function literal with receiver". Speaking generically, a type A.(B) -> C represents a function that can be called on a receiver object of A with a parameter of B and return a value of C. Or in your example:
requestBuilder: (ImageRequest.Builder.(size: IntSize) -> ImageRequest.Builder)?
We have a function requestBuilder which can be called on a ImageRequest.Builder with a parameter size: IntSize and returns another ImageRequest.Builder.
Calling this function is just like calling any other function with a lambda as a parameter. The difference: You have access to ImageRequest.Builder as this inside your lambda block.
Hope the following example helps understand lambdas with receiver type:
data class Person(val name: String)
fun getPrefixSafely(
prefixLength: Int,
person: Person?,
getPrefix: Person.(Int) -> String): String
{
if (person?.name?.length ?: 0 < prefixLength) return ""
return person?.getPrefix(prefixLength).orEmpty()
}
// Here is how getPrefixSafely can be called
getPrefixSafely(
prefixLength = 2,
person = Person("name"),
getPrefix = { x -> this.name.take(x) }
)
How do I implement the requestBuilder parameter?
Hope this part of the code snippet answers the above:
getPrefix = { x -> this.name.take(x) }
PS: These lambdas with receiver types are similar to extension functions IMO.

Error when trying to convert a list of objects in a string using reduce function

I am playing with kotlin language and I tried the following code:
data class D( val n: Int, val s: String )
val lst = listOf( D(1,"one"), D(2, "two" ) )
val res = lst.reduce { acc:String, d:D -> acc + ", " + d.toString() }
The last statement causes the following errors:
Expected parameter of type String
Expected parameter of type String
Type mismatch: inferred type is D but String was expected
while this version of the last statement works:
val res = lst.map { e -> e.toString() }.reduce { acc, el -> acc + ", " + el }
I do not understand why the first version does not work. The formal definition of the reduce function, found here, is the following:
inline fun <S, T : S> Iterable<T>.reduce(
operation: (acc: S, T) -> S
): S
But this seems in contrast with the following sentence, on the same page:
Accumulates value starting with the first element and applying
operation from left to right to current accumulator value and each
element.
That is, as explained here:
The difference between the two functions is that fold() takes an
initial value and uses it as the accumulated value on the first step,
whereas the first step of reduce() uses the first and the second
elements as operation arguments on the first step.
But, to be able to apply the operation on first and second element, and so on, it seems to me tha the operation shall have both arguments of the base type of the Iterable.
So, what am I missing ?
Reduce is not the right tool here. The best function in this case is joinToString:
listOf(D(1, "one"), D(2, "two"))
.joinToString(", ")
.let { println(it) }
This prints:
D(n=1, s=one), D(n=2, s=two)
reduce is not designed for converting types, it's designed for reducing a collection of elements to a single element of the same type. You don't want to reduce to a single D, you want a string. You could try implementing it with fold, which is like reduce but takes an initial element you want to fold into:
listOf(D(1, "one"), D(2, "two"))
.fold("") { acc, d -> "$acc, $d" }
.let { println(it) }
However, this will add an extra comma:
, D(n=1, s=one), D(n=2, s=two)
Which is exactly why joinToString exists.
You can see the definition to understand why its not working
To make it work, you can simply create an extension function:
fun List<D>.reduce(operation: (acc: String, D) -> String): String {
if (isEmpty())
throw UnsupportedOperationException("Empty list can't be reduced.")
var accumulator = this[0].toString()
for (index in 1..lastIndex) {
accumulator = operation(accumulator, this[index])
}
return accumulator
}
you can use it as:
val res = lst.reduce { acc:String, d:D -> acc + ", " + d.toString() }
or simply:
val res = lst.reduce { acc, d -> "$acc, $d" }
You can modify the function to be more generic if you want to.
TL;DR
Your code acc:String is already a false statement inside this line:
val res = lst.reduce { acc:String, d:D -> acc + ", " + d.toString() }
Because acc can only be D, never a String! Reduce returns the same type as the Iterable it is performed on and lst is Iterable<D>.
Explanation
You already looked up the definition of reduce
inline fun <S, T : S> Iterable<T>.reduce(
operation: (acc: S, T) -> S
): S
so lets try to put your code inside:
lst is of type List<D>
since List extends Iterable, we can write lst : Iterable<D>
reduce will look like this now:
inline fun <D, T : D> Iterable<T>.reduce(
operation: (acc: D, T) -> D //String is literally impossible here, because D is not a String
): S
and written out:
lst<D>.reduce { acc:D, d:D -> }

The result keeps saying: "Type mismatch: inferred type is Unit but String was expected"

I have ran the following code on play.kotlinlang.org but the result keeps saying: Type mismatch: inferred type is Unit but String is expected
(I made some changes from the original example code)
I have no idea why its inferring type is Unit.
I thought I announced the returning type as String.
Is the type of println() Unit or have I just wrote the whole ring function wrong?
fun main(){
val squid:(String)->String={str->println("I'm $str")}
//ring(::rabbit)
ring(squid)
}
fun rabbit(str:String):String{
println("I'm $str")
return str
}
fun ring(carrot:(String)->String){
carrot("a appetiting squid")
}
As Animesh pointed out you aren't returning a String n squid
val squid:(String)->String defines a lambda that takes in a String and returns a String
{str->println("I'm $str")}
A lambda will return the value on the last line of the lambda, but in this case that last value is println("I'm a $str")
println return Unit, or rather, it doesn't return anything (which is what Unit represents). So if you still want to print the String AND return it, rewrite it like so:
{ str ->
println("I'm $str")
str // This string is the value that will now be returned.
}
The lambda returns Unit because println returns Unit.
You can write that line as follows:
val squid:(String)->String={str-> str.also { println("I'm $it") }}
The standard also extension function lets you use the value in an expression (because it returns the value itself) but "also" do something with it first.

Kotlin: maxBy{} with optimum-value

Let's say I have the following code in Kotlin:
val min = listOf("hello", "", "teeeeeest").minBy { it.length }
What I understand from the implementation of minBy is that it tracks minValue in a variable and iterates through the whole collection and updates it once it finds an even smaller element.
In the case of Strings though, we know that no element can have a value smaller than 0, therefore the empty String "" is optimal and the iteration can be stopped.
Is there a way I can tell minBy (or maxBy) the optimal value so it can stop once that is reached? If not, how can I implement this most easily?
There's no function in the stdlib that can do this, but you can implement it as an extension function yourself.
By using the non-local return feature of inline lambda functions in Kotlin, you can implement it like this:
fun <T, E : Comparable<E>> Iterable<T>.minBy(theoreticalMinimum: E, keySelector: (T) -> E): T? =
minBy {
val key = keySelector(it)
if (key <= theoreticalMinimum) return it // Non-local return.
else key
}
Now you can use it like this, and it will never visit "teeeeeest":
val min = listOf("hello", "", "teeeeeest").minBy(theoreticalMinimum = 0) { it.length }

What does "with" mean in Kotlin?

I've read the docs on it 3 times and I still have no idea what it does. Can someone ELI5 (Explain Like I'm Five) it please? Here's how I'm using it:
fun main(args: Array<String>) {
val UserModel = UserModel()
val app = Javalin.create().port(7000).start()
with (app) {
get("/users") {
context -> context.json(UserModel)
}
}
}
with is used to access an object's members and methods without having to refer to the object once per access. It is (mostly) for abbreviating your code. It is frequently used when constructing an object:
// Verbose way, 204 characters:
var thing = Thingummy()
thing.component1 = something()
thing.component2 = somethingElse()
thing.component3 = constantValue
thing.component4 = foo()
thing.component5 = bar()
parent.children.add(thing)
thing.refcount = 1
// Terse way, 182 characters:
var thing = Thingummy()
with(thing) {
component1 = something()
component2 = somethingElse()
component3 = constantValue
component4 = foo()
component5 = bar()
parent.children.add(this)
refcount = 1
}
The documentation says:
inline fun <T, R> with(receiver: T, block: T.() -> R): R (source)
Calls the specified function block with the given receiver as its receiver and returns its result.
The way I think of it is that it is calling a function (the block) where this in the scope of the block is the receiver.
Whatever the block returns is the return type.
Essentially calling a method where you provide the implicit this and can return any result from it.
Here is an example to demonstrate:
val rec = "hello"
val returnedValue: Int = with(rec) {
println("$this is ${length}")
lastIndexOf("l")
}
The rec in this case is the receiver of the function call - the this in the scope of the block. The $length and lastIndexOf are both called on the receiver.
The return value can be seen to be an Int because that is the last method call in the body - that is the generic type parameter R of the signature.
The definition of with:
inline fun <T, R> with(receiver: T, block: T.() -> R): R (source)
Actually it's implementation is straight forward: The block is executed on receiver, which works for any type:
receiver.block() //that's the body of `with`
The great thing to mention here, is the parameter type T.() -> R:
It's called function literal with receiver. It's actually a lambda that can access the receiver's members without any additional qualifiers.
In your example the context of with receiver app is accessed in that way.
Besides stdlib functions like with or apply, this functionality is what makes Kotlin great for writing Domain Specific Languages as it allows the creation of scopes within which you have access on certain functionalities.
val citizen2 = Citizen("Tom", 24, "Washington")
val age = with(citizen2) {
println("$name - $age $residence ")
age = this.age + age
residence = "Florida"
age+10 // returns 58
}
println("${citizen2.name} - ${citizen2.age} - $age - ${citizen2.residence} ")
data class Citizen(var name: String, var age: Int, var residence: String)
Output:
Tom - 24 Washington
Tom - 48 - 58 - Florida
Note that :
We can access age property of citizen(receiver object) with this.age or age
last line(age+10 in this example) in the lambda of with() returns.
With is used to apply several operations to an object or access object's methods e.g. in this example we are accessing String's capitalize() extension method
data class Person(val name:String)
fun main(){
val person = Person("john doe")
with(person) {
println(name.capitalize()) // output John Doe
}
}
Under the hood with is a higher-order function. Here we are saying with the Person name call capitalize ( ). We don’t actually need ‘this’ because it is implicit and can be removed