Style question: Thoughts on whether to use `x.run ...
# getting-started
d
Style question: Thoughts on whether to use
x.run { doY() }
vs
with(x) { doY() }
vs
x.apply { doY() }
when the result doesn't matter? Does your opinion change if it's a multiline lambda?
I know of course that
apply
has different behavior than the other two if the overall expression result is returned, but in this case it is expected that the expression result is ignored entirely.
Also, to throw in the "here's a worse way":
x.let { with(it) { doY() } }
Oh, and does your thinking change if
x
is instead an expression itself? For example,
MyBuilderImpl().run
vs
with(MyBuilderImpl())
My current thinking is that: 1.
apply
looks good for single line lambdas on simple references. 2.
with
looks good for the
MyBuilderImpl()
example 3.
run
looks good for multiline lambdas. This is, of course, somewhat subjective, so hoping others can give better reasoning, or tell me why the agree with me 😉
e
I basically never use
x.run
, unless it's something like
x?.run
.
with(x)
reads the best to me, and
x.apply
has its uses when returning
x
.
👍 1
k
If
x
is a long expression like
a + b() + c.foo(d)
, then by using
with(a + b() + c.foo(d)) { ... }
you start the sentence with
with
, showing that you're about to do something with the expression. With ``(a + b() + c.foo(d)).run { ... }` all you see at first is a long expression and you don't know what you're about to do with it until you see
run
. It looks more natural to English speakers to use
with
because in English we usually put the object later on in a sentence. This may be different in some other languages where the object comes first.
p
My preference tends to lean towards the semantics of the statement. For something like the builder pattern,
.apply {}
makes sense as you're "applying" a change to receiver. For something that's a side-effect, I'd rather use
x.also { it.doY() }
as it's clear that you're "also" doing something.
if you really wanted to lift
x
to the receiver, then
x.also { with(it) { doY() } }
would read nicely although with extra nesting. but again, that makes more sense when part of a chain - which if you have x as a variable it's probably cleaner to just use
with(x) { doY() }
d
Thanks all for the thoughts.
@ephemient The other times I use
run
is just as a way to create a smaller scope for a val/var. This is rare though, as often times its better to just extract a new method in the first place.
@Klitos Kyriacou That makes sense.
@phldavies That makes sense, and is often how I approach it.
e
run
without a receiver reads fine imo but it's usually unnecessary. smaller local scope doesn't really matter in Kotlin, unless you're running into shadowing (in which case maybe choose better names instead)
unlike c++, there's no raii and scope doesn't affect garbage collection. if a local variable is not read past a certain point, that value may be garbage collected, even if it is still "in scope"
d
It's usually in Swing UI code where I have two similar-but-different sections where shadowed name makes sense.
Like I said though, often times it makes more sense just to factor out a new method.