So the past few days I tried to do a deep dive on ...
# coroutines
c
So the past few days I tried to do a deep dive on flow to better my understanding (long overdue šŸ˜… ) can anyone correct me if im wrong here on any of my points? 1. channels were the "precursor" to flow. they are hot. 2. Flows are cold and asynchronicity is built-in since its built on top of coroutines 3. flows handle "backpressure" out of the box, because an item wont be emitted from a flow until it's actually been collected. 4. Sequence is "similar" to a flow but its synchronous. one item is "emitted", worked on, then moves to next item. 5. list is similar to sequence, but all items come all at once 6. flow is simpler than rxjava. leads to being technically faster. less operators to memorize (everything is async instead of one operator for sync and another for async). easy to error handle (ie just one catch opereator)
z
1. Channels did come before flows, but I’m not sure how accurate it is to describe them as a ā€œprecursorā€. They are an entirely different tool. You cannot use channels to do what flows do (although theoretically you could probably build something similar using them), and flows rely on channels as a more primitive building block for certain things. 2. Flows can be hot too (MutableSharedFlow). 3. They do, but a more accurate wording would be because the emit function won’t return until the collector returns, even if it suspends. 4. Yes, and I wish this comparison were made more often. Conceptually they’re different colors of the same thing even though they have very different APIs and implementations. 5. Sequence is most comparable to Iterable, of which List is a subtype. List implies many more things about data and behavior than Iterable or Sequence do so I would be hesitant to compare sequence and list directly. 6. Flow is simpler in itself, but only because much of the complexity is factored out into coroutines, channels, etc. I don’t know if it’s faster – rxjava has been pretty well optimized over a long time, and in some cases might be easier to optimize because all the complicated behavior is self-contained whereas coroutines have to handle all kinds of applications of which flow is only one. So I have no idea which is empirically faster for what use cases. Error handling is different but has its own gotchas. It is generally easier to write async operators and generally perform async work with flow though, because doing so can make use of more flexible and convenient lower-level coroutine APIs and don’t have to express everything specifically in terms of flows.
šŸ‘ 5
ā¤ļø 1
c
Re: 6 I was basically just getting that info from this talk
e
Sequence is effectively a wrapper type around an Iterator, so that it can provide extended functionality without confusing other usages of Iterator Iterable (supertype of Collection, List, Set, etc.) is expected to be iterable any number of times; Iterator is only expected to be iterable once, and Sequence is only guaranteed to be iterable once (although some sequences may be iterated multiple times)
and interesting, it's been a while since I did anything with RxJava but the last time I tested, I think it dispatched between threads faster than flow/coroutines (although that could have just been an artifact of my setup). having
suspend
built into the language creating state machines automatically is 99% easier to work with than having to do it yourself in Rx though, and in the case where you're not changing dispatchers,
Flow
is literally just a regular function call which is hard to beat
y
I concluded backpressure for Flows is simpler. Or maybe more that they flipped the default, rendezvous vs buffered? I found myself dealing with explicit requests tokens(?) more in Rx.
c
i never understood backpressure in rx, so im a bit out of my depth there. but it was interesting to read from roman that ~"backpressure is handled by default"
y
https://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/org/reactivestreams/Subscriber.html has an explicit
Subscription.request(long)
concept as part of the Core API.
It's mostly hidden when you use APIs like RxJava or Reactor operators.
But if you want backpressure over the network, and it might vary over time, say based on network conditions, it's hard to replicate this with Flows. At least with my clumsy naivety with them.
z
Backpressure in rx: • Subscriber: I want to subscribe to you • Source: Ok let me know when you’re ready for items. • Sub: Ok please give me 1 item • Src: <emits 1 item> ok here you go, now I’ll go make a coffee. • Sub: … • Sub: … • Sub: …ok pls 1 more • Src: <emits 1 item> • Sub: ok now just give me everything you got • Src: <emits n items> Backpressure in flow: Collector: I would like to collect you Flow: Ok … here’s the first ite- Collector: <processes item> Collector: … Collector: … Collector: ok done, returning Flow: -m. And here’s the next ite- Collector: <processes item> Flow: -m.
šŸ‘Œ 2
Rxjava is kinda like tcp, the downstream explicitly rate limits the upstream by requesting one or more items at a time. The default is to request infinity, and just assume the subscriber will handle them very quickly. Flow is less flexible, in a way, because a collector can only request a single item at a time, and always does so. Flow gets to make the rate limiting decision while it’s handling an item though, whereas rxjava has to make it ahead of time.
šŸ‘€ 1
y
Isn't
buffer()
the way to request N? (for Flow)
> requesting one or more items at a time It doesn't need to be one, you could have some policy say request 3, and replenish after 2 are consumed. Avoid a storm, but also generally avoid introducing delays normally. (For Rx)
z
Yes, but even when emitting to buffer the same still applies – adding to the buffer is just an operation that potentially happens without suspending.
Not sure what you mean. I said one or more.
y
Ahh, yep.
I read requesting one and then one more item at a time. Extra words via my brain. šŸ™‚
šŸ˜‚ 1