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…