Am I missing something or there's no version of zi...
# coroutines
d
Am I missing something or there's no version of zip for 3,4,5, iterable flows? there is a top-level
combine
, but no top-level
zip
. I have a
List<Flow<Int>>
and want to zip them, what is my best option?
g
There is no top level zip or zip for more than 2 flows, maybe worth to fill feature request
e
we do have a data structure for Triple, so it would be possible to
Copy code
fun zip3(a, b, c) = a.zip(b).zip(c) { (a, b), c -> Triple(a, b, c) }
or have a more efficient implementation of the same signature, but there is no larger heterogeneous tuple type… what should zip4/zip5/etc. return?
oh we're talking about #coroutines and there is no Pair-based return anyway, only transform-based, so I guess it's fine
g
Yep, combining multiple zip will work of course, but it's not very friendly api
👍🏻 1
d
It doesn't have to return a "bag"-like type like Triple, it could fold N values into one or do other combination stuff. I've outlined my usecase here (found a github issue for this): https://github.com/Kotlin/kotlinx.coroutines/issues/1887#issuecomment-1029279694
Also it's strange that it's currently asymmetrical with
combine
and also usually these many-values combinators are present in libraries supporting zip: rx has this, haskell's stdlib has this, scala etc etc. Coroutines have this for combine, but not for zip.
g
I see your comment, but if you need only “completed”state, you don’t really need zip, right? Because you can just use filter + comibne
Or even not combine, essentially you can even do it with filter + first and just use suspend function to await all results
d
Hmm. I need to wait until all 4 completed not only initially, but afterwards too. I.e. those tasks are restartable, I nned this behaviour:
Copy code
1finished 2finished 3finished -> combined result
1finished 2finished 3finished -> combined result
combine would give me
Copy code
1finished 2finished 3finished -> combined result
1finished -> combined result
2finished -> combined result
3finished -> combined result
I.e. I need zip's semantics which waits for all to complete in stages.
g
those tasks are restartable, I nned this behaviour
So all your 3 separate flows are restarting and every time you want to combine their latest result?
It sounds kinda strange, what if one of them will restart earlier than others?
ah, so you control it using this start() method
so you have to manage correctness of this “start()” manually and avoid calling it if some of them is not completed
Not sure that I like this Task interface, it’s very error prone and hard to manage
why not change it to:
Copy code
interface Task<T> {
  fun start(): Flow<Either<T, Throwable>>
}
Though, I even not sure that you need Flow here, because it always just emit Either
so this will be enough:
Copy code
interface Task<T> {
  fun start(): Either<T, Throwable>
}
it easily integrated with any Flow/suspend function code and much more straightforward to suse
d
Well, this separation of results state flow and start() allows to build highly declarative UI's, basically UI component subscribes to results flow once and then different parts of UI can call `start()`: for example on screen open and then on button clicks or pull-to-refreshes. Also multiple screens can be subscribed to this
Flow
without needing to know about
start()
. One screen calls start, other screens get result reactively. It's a very common pattern for us which was derived across several years of writing very complex apps. For example I've seen this pattern also in Chris Banes' tivi sample but it's not yet organized there, it has start() and flows separately in interfaces, they often go together. We have only observed this and bundled them neatly, which simplified the code.
g
Well, this separation of results state flow and start() allows to build highly declarative UI’s,
I don’t agree with this, it’s not declarative
Also multiple screens can be subscribed to this 
Flow
 without needing to know about 
start()
But what is actual use case of this? To know about all restring requests?
If you really want it to be declarative, I would then rather hide start() inside of this flow, which return different types like: TaskIdle TaskProgress TaskCompleted Where start() exist only on Idle or Completed state
at least it will be safe in terms of types
But I still disagree that this interface makes sense even in this case, for example it missing such a basic thing as cancellation or handling of multiple start() (should it restart? should it throw error?)
d
But what is actual use case of this?
I have a setting screen which starts an upload, show loader, then return to pervious screen which shows all active uploads. they both are subscribed to
task.state
and show current loading state. other screens a free to do so to. also both screens can have button to "restart" failed upload, which has
onClick = { task.start() }
In the issue I've outlined only what's important for the issue. Of course we also have
val state: Flow<TaskState> // idle/working
missing such a basic thing as cancellation or handling of multiple start() (should it restart? should it throw error?)
we have all this. Task is a building block, while there's a framework around this.
Actually I'm wrong, the building block is a simple bundle of
state: Flow<Idle|Running>
and
results: Flow<Either>
and
Task
is the thing adding a
start()
to it which was born out of observation that many-many screen operations are restartable: fetching content (initially and after pull-to-refresh), etc etc. Task is not necessarily used everywhere, but pair of state+results is used often.
I don’t agree with this, it’s not declarative
Prepared a tiny (simplified) example. Do you think this is not declarative? https://gist.github.com/dimsuz/179d50e4eb04a0e5dba2928362429cb4 Task is not a UI state construct (you seem to think that), it's from "domain" layer, more generic. UI only happens to render some of its state and call some of its methods as a side-effects.