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"
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.
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
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 amap
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.