https://kotlinlang.org logo
Title
p

pablisco

04/23/2021, 9:32 AM
I have this use case: There is a number of network calls that are sequential, so each needs to know about the previous one. My first thought was to use
generateSequence
however it doesn’t support suspend operations 🙃 Anyone knows of an alternative for such case?
a

Alex Vasilkov

04/23/2021, 9:44 AM
Shouldn’t it be just
suspend fun call(): Result {
  val res1 = call1()
  val res2 = call2(res1)
  val res3 = call3(res2)
  return res3
}
?
👍 1
p

pablisco

04/23/2021, 9:45 AM
It’s a feed so we need to fetch until there are no more items
a

Alex Vasilkov

04/23/2021, 9:45 AM
Use a loop maybe? 🙂
p

pablisco

04/23/2021, 9:45 AM
And yeah we can use a while loop, but I was looking for a more functional approach without mutability
tailrec may be a good option, potentially
a

Alex Vasilkov

04/23/2021, 9:55 AM
I think tailrec will use a mutable variable as well once compiled. I’m not sure why local mutable variables are necessary an evil though
p

pablisco

04/23/2021, 9:57 AM
something like:
tailrec suspend fun call(
  result: List<String> = emtpyList(),
  lastItem: String? = null
): List<String> = fetch(lastItem).let { new ->
  when {
    new.isEmpty() -> result
    else -> call(result + new, new.lastOrNull())
  }
}
I’m not sure why local mutable variables are necessary an evil
Nobody said it’s evil, JUST what I want to do
a

Alex Vasilkov

04/23/2021, 10:03 AM
something like: …
It seems like this code does not use tailrec because of
.let
call. With fixed tailrec Kotlin generates something like this:
@NotNull
   public static final List call(@NotNull List result, @Nullable String lastItem) {
      while(true) {
         Intrinsics.checkNotNullParameter(result, "result");
         List var2 = fetch(lastItem);
         if (var2.isEmpty()) {
            return result;
         }

         List var10000 = CollectionsKt.plus((Collection)result, (Iterable)var2);
         lastItem = (String)CollectionsKt.lastOrNull(var2);
         result = var10000;
      }
   }
So it still uses a mutable
result
variable after all.
p

pablisco

04/23/2021, 10:03 AM
Also, the fact that mutability is used underneath is like saying that types are erased after compiled so why bother to design with generics 🤷 It’s about designing for less failure I didn’t say it’s evil, but mutability can definitely be a source of evil
🔝 3
So it still uses a mutable 
result
 variable after all.
Let’s use
List<Any>
too, right? 😂
Any who, thank you for trying to help
without the let (and the typo which also makes it not compile) it looks like this:
tailrec suspend fun call(
    result: List<String> = emptyList(),
    lastItem: String? = null
): List<String> {
    val new = fetch(lastItem)
    return when {
        new.isEmpty() -> result
        else -> call(result + new, new.lastOrNull())
    }
}
l

louiscad

04/23/2021, 10:18 AM
Mutability is fine when its scope is small, when it cannot lead to race condition (ideally check with stress tests), and when it's abstracted away to avoid future breakage of the two "rules" I just mentioned.
p

pablisco

04/23/2021, 10:24 AM
Yeah, true. That’s why I rather avoid mutability as much as possible to not have to worry about the size of the scope or raced conditions. I’ll use mutability as a last resource if there is no better option or if there is a need for optimisation. Mutability is often an early optimisation IMHO