Hey folks, I 've published an article about scope ...
# feed
g
Hey folks, I 've published an article about scope functions. Hope you like it, and any feedback is much appreciated! https://medium.com/@george.719pap/opinionated-one-liners-in-kotlin-dfc628b4847a
c
A bit of feedback… First, you should really emphasize the danger in using scope functions. We're all magpies, excited by shiny new things, but like all syntax sugar, if you consume so much that you become obese, it's not so great anymore. Scope functions are incredibly versatible, and it's very easy to use them in places where not using them would lead to clearer code. Let's look at a few examples:
Copy code
fun example1(): Node? = Node().let(Util::process)
vs
Copy code
fun example1(): Node? = Util.process(Node())
is the first example really better code, here?
The
.let
scope function shines the most when it is used for simple one-line side effects.
No, that's
also
.
also
is used for side-effects,
let
is used for pure transformations. You can see this by looking at their signature (simplified):
Copy code
fun T.let(block: (T) -> T): T
fun T.also(block: (T) -> Unit): T
let
is
T -> T
, meaning that its return value is passed to the rest of the computation.
also
is
T -> Unit
, meaning that the lambda returns no useful information (thus it must have done something else, which is a side-effect). You can remember this by comparing it with lists:
Copy code
fun List<T>.map(block: (T) -> T): List<T>
fun List<T>.forEach(block: (T) -> Unit): List<T>
e.g., if you would use
map
with a list, you should use
let
; and if you would use
forEach
, you should use
also
.
👍🏾 1
👍 2
Also, have you run your code examples?
Copy code
fun example2() {
    val scope = createScope().let { it.name = "test3" }
    // ... more code
}
Here, the type of
scope
is
Unit
, it's not the result of the
createScope
function. In fact, the returned value of
createScope
is lost and garbage-collected at the end of the line. This is because assignments are not expressions, so the type of
it.name = "test3"
is
Unit
. Since
let
is used for pure transformations, the result value is
Unit
. If you had used
also
in this example, then
scope
would indeed contain the return value of
createScope
.
You do mention the risk with scope functions, but I think you could go further. With scope functions:
Copy code
fun example7(): Node? {
  // ... code
  // ... multiple lines of code
  return Node().let { process(it) /* returns new node or null */ }
}
Without scope functions (your version):
Copy code
fun rewriteExample7(): Node? {
  // ... code
  // ... multiple lines of code
  val node = Node()
  val newNode = process(node)
  return newNode
}
Without scope functions (my version):
Copy code
fun rewriteExample7(): Node? {
  // ... code
  // ... multiple lines of code
  return process(Node())
}
The rest of the article is very good 👍
g
Thanks for the feedback!! I agree that we are excited about cool APIs. • About the first example, my intention was to show an ok approach, not a better vs worse approach. • You are right about let, probably it needs rewording. Though I have seen it being used as a side-effect and tbh I do not mind it, in case you do not care about the result ofc. • Thanks for catch on the example!!, Somehow I missed that.
c
Though I have seen it being used as a side-effect and tbh I do not mind it, in case you do not care about the result ofc
But it's not meant for that, that's
also
's job. If you're using
let
to do this, you will have bugs where the wrong value is returned—and there was one in your examples. There are so many scoping functions because each one has a specific role. Using them with the wrong role works most of the time, but it makes code less readable (because you don't know if it's used correctly) and it can introduce bugs.
1
k
It's a good article, and just to add to Ivan's comments, this bit:
Copy code
return Node().let {
    swap(it)
    it
}
can be shortened to
Copy code
return Node().also { swap(it) }
c
Or even just
Node().also(::swap)
🙂
💡 1
g
Thanks kyriako, also nice catch!
Thinking a bit about the
.let
for side-effects. In case you want to explicit return Unit, then it is ok I believe. Wdyt?
Copy code
fun explicittest2() {
    scope.let { it.name = "test3" } // returns Unit
    // ... more code
}
c
Copy code
fun foo() {
    scope.name = "test3"
    // ... more code 
}
😂 2
g
Yea my example is really bad. Lets pretend it has an if-else or something inside. Well without
?
probably in all cases it wont make sense
c
I'm a bit strict with this, but IMO the only case where
let
should be used is when calling a single conversion function (e.g.
it.toDto()
). In all other cases, I avoid using it, code is almost always simpler without it
💡 2
I'm less strict with
also
because it doesn't impact data flow so it doesn't make the code harder to debug
y
let is more like:
Copy code
fun T.let(block: (T) -> R): R
Which tells you that its return type has nothing to do with its input, which is good! It tells you that you can change the return type with it!
c
Ah yes sorry, my bad same with
map
, the rest of my arguments still stand 🙂