Sam
09/18/2024, 9:40 AMrepeat()
operator? i.e.
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.Dmitry Khalanskiy [JB]
09/18/2024, 9:48 AMSequence
.Sam
09/18/2024, 9:55 AMrefresh()
function and I want to call it every ten seconds:
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 versionSam
09/18/2024, 10:00 AMSam
09/18/2024, 10:00 AMFergus Hewson
09/18/2024, 10:01 AMDmitry Khalanskiy [JB]
09/18/2024, 10:02 AMf.collect {}
? If so, seems like we do have an issue request for that: https://github.com/Kotlin/kotlinx.coroutines/issues/3680Sam
09/18/2024, 10:06 AMrepeat()
in a separate place from the delay. Maybe I can come up with a better example:
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")
}
Sam
09/18/2024, 10:08 AMdelay(interval)
would be somewhere inside the displaySlideshow
implementation, so it's not really coupled to the repeat()
Dmitry Khalanskiy [JB]
09/18/2024, 10:17 AMFlow
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.Sam
09/18/2024, 10:33 AMList
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.Sam
09/18/2024, 10:35 AMDmitry Khalanskiy [JB]
09/18/2024, 10:43 AMrepeat()
is mostly useful with take(n)
following it, maybe it could be joined together into repeat(n)
.ross_a
09/18/2024, 11:08 AMretry
& 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.ross_a
09/18/2024, 11:18 AMretry
.ephemient
09/18/2024, 12:07 PMcycle
, as matching https://docs.python.org/3/library/itertools.html#itertools.cycle and https://hackage.haskell.org/package/base/docs/Prelude.html#v:cycle and https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.cycleross_a
09/18/2024, 12:12 PMross_a
09/18/2024, 12:13 PM