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

Thanks. Your wording is better, I changed it.

It is a typical source of confusion. I blogged about it couple of months ago: Intention of Kotlin's "also apply let run with"

1 Like

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

Hope you enjoy it.

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.

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)
}

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.

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

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.

For that use case, I prefer apply:

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

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.

2 Likes

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…

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

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.

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)
}

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

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

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?

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

But Optional is not a super type of Iterable or Collection. The let method is on every object. So the fact that map already exists on Optional is also an argument against using map as name for let.

Currently:

  • collection/iterable-like types have a map method to map their content (each item one by one)
  • Optional has a map method also to map its content (a single item)

They also both have the global let method, like any other object, to map themselves, not their content. So I don’t believe the langage designers could have named this method map, because Optional and collections would have had 2 overloads of it: one that takes the item type as input, and one that take the collection itself (or the Optional) as input.

Actually from a functional point of view both Optional and Collection are Monads, which is why they both have a map method. Since nullables in Kotlin are kind of a “primitive” type they dont have that.
For me a clear indication that KIotlin is not a functional language, but a OOP language with functional elements.