Is there an open (or closed) issue for a flow `rep...
# coroutines
s
Is there an open (or closed) issue for a flow
repeat()
operator? i.e.
Copy code
fun <T> Flow<T>.repeat() = flow {
  while(true) emitAll(this@repeat)
}
Seems like something that would come up a lot, but I haven't found anything by searching.
d
I haven't seen such a request before. Also, I don't think we have this function even for
Sequence
.
s
Interesting! I can think of a few times when it would have been a very useful building block for me. Say I have a
refresh()
function and I want to call it every ten seconds:
Copy code
suspend fun refresh(): Unit = TODO("something")

val f = ::refresh.asFlow().repeat().onEach { delay(10_000) }
Obviously I could do the same with an explicit loop, but I kind of like the declarative version
Maybe I'll create an issue for it... I'm not sure.
It's in RxJava, but that doesn't mean necessarily mean it'd be idiomatic in Kotlin
f
.onEach { delay(10_000) } ?
d
And then just
f.collect {}
? If so, seems like we do have an issue request for that: https://github.com/Kotlin/kotlinx.coroutines/issues/3680
s
Thanks, the link is useful. I still like the fact that flows can separate the concerns though. It's nice to be able to define the
repeat()
in a separate place from the delay. Maybe I can come up with a better example:
Copy code
val images = flowOf(image1, image2, image3)
  
suspend fun main() {
  displaySlideshow(images.repeat(), interval = 5.seconds)
}

suspend fun displaySlideshow(slides: Flow<Image>, interval: Duration) {
  TODO("UI stuff")
}
In that example the
delay(interval)
would be somewhere inside the
displaySlideshow
implementation, so it's not really coupled to the
repeat()
d
I personally wouldn't write it like this, as I'm a believer in the principle of least power.
Flow
is a potentially infinite asynchronous stream of values;
Sequence
is just a potentially infinite stream of values;
List
is an ordered collection of values. If you have three photos, that's just an ordered collection of values. that's all.
displaySlideshow
in my version would also just accept a
List
. The reason is simple: it's easy to generalize functions, it's hard to de-generalize them. Let's say I want to change the behavior of
displaySlideshow
somehow: for example, I want to ensure that the photos are in random order, but one photo is never shown twice in a row. If
displaySlideshow
accepts a
List
, that's trivial. If it accepts a
Flow
, I sit down and think: "Oh, okay, well, I guess I need to go through the flow with some window, collect several values, weed out the duplicates... Is this algorithm really random? Let me run some simulations..." Then I realize
displaySlideshow
is only ever called with a constantly repeating flow of three photos and feel stupid for having wasted so much time on useless things. This has actually happened to me multiple times: the correct solution for introducing functionality was just to specialize the function to the values it's actually called with. Since then, I'm wary of such generalizations.
s
That makes sense, and I agree I'd stick to a
List
if the images are known ahead of time. Maybe my example would have been better if I had the
images
flow use some dynamic source that downloads each image on-demand. If
displaySlideshow()
accepts a
Flow
, then one consumer can call it with
flowOf(image1, image2, image3)
and another can call it with
::downloadRandomImage.asFlow().repeat().take(50)
. The laziness could be very useful, to prevent wasted downloads in case the user exits the app before the slideshow is complete. Anyway, more generally, I'm sure there are places where infinite flows are useful, even if my example isn't a great one. The
repeat()
operator would basically be a way to convert a finite flow into an infinite one, without changing any of its other properties.
I'm not really campaigning for or against its inclusion; just curious really. Perhaps people who've used the corresponding RxJava operator would have an opinion on whether they miss it in Kotlin, or whether it's clearer just to write out the loop.
d
Sure, I'm also not campaigning for or against anything, we are just exploring the problem space collaboratively. This is important to do even if it's obvious that the function is useful and should be added, as there are several ways to add anything. For example, if
repeat()
is mostly useful with
take(n)
following it, maybe it could be joined together into
repeat(n)
.
👍 2
❤️ 1
r
If it were added I'd like it to be similar to
retry
&
retryWhen
(albeit, without a
cause
) where that's where you can perform your delay and, in the case of the
When
variant, return true/false.
I have actually implemented something similar, for price streams. The upstream may complete after some period of time as it has a maximum stream length, and in some cases we just want it to resubscribe and continue, other times we don't. We don't consider this an exception in the same vein as a connection problem though I'm sure you could model it that way and use
retry
.
r
to be honest I'm not sure either repeat or cycle quite work as names as you're not necessarily going to get the same items on each reestablishment of the upstream
repeat/cycle indicate a loop - ABC/ABC/ABC - where really you can get ABC/DEF/HIJ