Hi. There is some interesting discussion in my tea...
# announcements
a
Hi. There is some interesting discussion in my team about the usage of the functions
also
(https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/also.html) and
run
(https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run.html). I understand what they are doing but never had the need to use them. Do you use them at all? can you tell a valid case where you used them and it made your code better?
p
also
provide a way to do some side-effect in a fluent style, this could be useful with some old java lib, for example when you have to configure and pass a
java.util.Properties
(despite in such case I would prefer
apply
)
Copy code
aCall(Properties().also {it.put("someProp", "someValue")})
run
: I used it only in rare case, to enhanced code readability when doing some test
val isValid = anObj.anotherObj.aString.run{length>6 && strartWith("foo") && endsWith("bar")}
m
I use .run as in this example:
Copy code
ar p : String? = null
    p?.let { println("p is $p") } ?: run { println("p was null. Setting default value to: ")
        p = "Kotlin"}

    println(p)
//Prints
//p was null. Setting default value to: 
//Kotlin
a
.also()
I’ve used to do things like logging messages after evaluating something, or for example:
pictures.remove(picture)?.also { eventSink.emit(it) }
.run
I’ve used with Protobuf builders (from a Java lib):
DataType.newBuilder().run { prop1 = value1; prop2 = value2; build() }
Compared to
let
and
apply
, I’ve used them very rarely
a
Thank you all !!
s
I've used it here
s
My rules of thumb:
with
(or
run
): Executing code in a context (eg great for extension functions with multiple receivers) `apply`: Configuring an object, replacement for builder pattern `let`: Transform/translate, much like the
map
function for a List, Set, etc `also`: For executing side-effects (logging, writing, UI, etc)
Shameless myself promotion 😀 https://link.medium.com/1GQJJSYlP9
👍 2
n
Note that free function
run
is totally unique, since it's the only scope function that's not tied to one specific object
I find it useful when you need to work with multiple objects, to do some computation, instead of just one. Creating objects inside the
run
lambda restricts their scope, prevents people from accidentally using them later.
Also, probably a controversial take, but I suggest just forgetting that
with
exists. It's just an inferior version of member function run; less auto complete friendly and doesn't support ?.
As this thread proves, there's already a fair number of scope functions and opinions on when to use which, so if you have the opportunity to completely remove one, I think it's worth taking
s
I use
with(object)
for contextual computation (eg multiple receivers). This will work with
object.run
as well, but in my opinion it seems clearer to read 🙂
I use
object.run
where i just want to avoid typing
object.
too many times 🙂
n
but why is it clearer to read? because it's a word you like more?
I'm not a big fan of that reasoning, it's just kind of conventions for the sake of conventions at that point.
We don't give the same function different names so that it reads better used in different contexts, normally
s
With the object ‘obj’, call the multiple-receiver function ‘hello’ on ‘otherObj’:
with (obj) { otherObj.hello() }
Run a bunch of methods on object ‘obj’:
obj.run { someMethod(); otherMethod(); yetAnotherMethod() }
n
Okay, so just wording 🙂
s
Yup 🙂
n
functionality wise, it doesn't offer anything
This isn't something we do usually when writing code, so I'm not sure why scope functions should be different 🙂
s
With that, i agree completely. functional wise, these are the same.
n
I just think the value of having to keep fewer scope functions in your head, is pretty good.
s
I probably wouldn’t have missed the
with
function if it were never there 🙂 But the plain, non-extension function,
run
is a mystery to me. Why
run { ... blah ...  }
or
run(lambda)
, if you can just do
… blah …
or
lambda.invoke()
?
n
The invoke being at the end makes it very easy to forget to write it
and then when you forget it can cause really confusing errors
.. blah ..
, the reason as I said before, is you can scope variables
which is very useful
if you only need to compute
foo
and
bar
so that you can do
val baz = makeBaz(foo, bar)
, and for no other reason
Then it's nice to do:
Copy code
val baz = run {
    // compute foo, compute bar
    makeBaz(foo, bar)
}
another example is that you can perform work that requires a
MutableList
, but then return from the lambda to a
List
. So the code outside the
run
block never is able to mutate the list.
Another reason is that for short bits of code,
fun foo() = run { ... }
saves you having to write the type, and effectively gives you type deduction
🙂
s
Often, though, I would refactor the plain
run { …. }
into a new method/function all together. But that can be said for the extension function
run
as well 🙂
👍 1
n
Well, sure, refactoring is valid, but somewhere, eventually, you need to implement something 🙂
run { ... }
isn't really an alternative to refactoring. It's an alternative to letting the code sit inline.
=
run
or
=
other scope functions, to define a function, I really like, it's nice not to have to write the type
Sometimes you have to write some functions that are relatively trivial to forward things around, really helps
a
Thank you for your comments!! I’ve learnt a lot !!