Spreadsheet for differences between let, with, run, apply and also


#1

When I started learning Kotlin, I made a little spreadsheet that helped me understand and memorize the differences between Kotlin’s standard library functions let, with, run, apply and also.

I think it might be of help to other people also, so here it is:


Let vs. with vs. run
#2

Nice one!

One note: better to say that run/let/with return the “result of lambda” rather than “any object”.


#3

Thanks. Your wording is better, I changed it.


#4

It is a typical source of confusion. I blogged about it couple of months ago: https://www.zeljko.link/2017/07/kotlin-also-apply-let-run.html


#5

I posted an article about Kotlin’s scoping functions on medium:

Hope you enjoy it.


#6

I would tell people concentrate on the main 4 and don’t bother with with. I find with redundant and never use it.It is then easier to remember the other 4.


#7

They all are redundant, in some way.

But I agree, I don’t like with that much also. I use it only as part of expression body functions that don’t return something, like so:

fun doStuffWithPerson(person: Person): Unit = with(person) {
  doStuffWithName(name)
  doStuffWithAge(age)
}

But the same could be done with run also:

fun doStuffWithPerson(person: Person): Unit = person.run {
  doStuffWithName(name)
  doStuffWithAge(age)
}

#8

I don’t think they are redundant at all. I was playing with Kotlin back in the days before apply and also were added (just happened to be looking at my old posts and my second post here was requesting what became the apply method).

When you start nesting them it is very handy to be able to choose beween this or it. I actually proposed variations on apply and run that added an additional parameter but I think the addition of also (referred to as tap in that thread) was considered sufficient.


#9

I didn’t mean to say they are redundant. I meant they are somewhat not less redundant than “with” function.


#10

I find with incredibly useful. It allows terse initialization of a new object:

val thing = with (ANewThing()) {
    aProperty = value1
    anotherProp = value2
}

It’s especially nice when dealing with Java interop, when you have a class that has a bunch of clunky initialization methods & does not use a builder.


#11

For that use case, I prefer apply:

val thing = ANewThing().apply {
    aProperty = value1
    anotherProp = value2
}

#12

The power of with is in fact really understated. It de-facto allows context oriented programming. For example you can add some additional features to existing objects when running them inside specific context. Like this:

object ContextA{
  val <T> Map<String, T>.something
    get() = this["A"]
}

object ContextB{
  val <T> Map<String, T>.something
    get() = this["B"]
}

val map Map<String, String> = ...

with(ContextA){
  doSomething(map.something)
}

You can even pass the context as a parameter. I find it really fascinating.


#13

In fact, our two examples are not the same – and to be fair, mine is wrong. Because with returns the value from the lambda, my thing will actually end up with the value Unit. In order to fix it, I would have to do:

val thing = with (ANewThing()) 
{ 
    aProperty = value1 
    anotherProp = value2 
    this
}

Which is kind of hideous. BUT! Had I been in my right mind when I wrote this, I might have used a more palatable example:

val immutableThing = with (ImmutableThing.builder()) 
{ 
    setAProperty(value1)
    setAnotherProp(value2) 
    this.build()
}

Which illustrates what I meant with the whole Java interop.

All of which is largely moot, since @darksnake has already gone six levels deeper than the rest of us…


#14

Replying too late… but i like this article :+1:


#15

I’m late to the party, but shouldn’t let be called map?
I believe it would feel more natural, as everyone understands that the input is the receiver, and accessed inside the lambda as a parameter, and that the return value is the lambda’s value.

Defined on nullable types, it would even be an equivalent to a Optional/Maybe’s flatMap.


#16

Yes, let is analogous to Optional’s flatMap method. I was also thinking that it could have gotten a better name. But you need to be careful not to limit usecase with a name that is too specific.

This is one of the use cases that would look strange with map as name:

car.passenger.let { person -> 
   log(person.name)
   anotherSideffectWith(person)
}

#17

But that would be a strange use case for let, because it’s not using the return value.
It makes more sense to use also in this case. Or does anotherSideffectWith() return something?

Maybe the reason why it’s not called map is that map is already commonly used on collections, and it would be very strange to have it behave differently there compared to every other object.

Anyway, I find the names confusing but can’t seem to find anything short that actually reads natural


#18

I had a moment with this this week. It helped me realize… let is basically map for single objects. (map is map for collections.)


#19

Exactly my point, yes, but I was rather wondering why it was named let and not map.

The need for a different name seems obvious for objects of type Iterable, Collection, etc. so I get that it can’t be named map without being confusing. But why let?


#20

I don’t agree with that reason. The type java.util.Optional has a map method, too, without being confusing.