Decouple switching logic from switchMap operator and consistent naming
See original GitHub issueWe currently have a switchMap { ... }
operator that is similar to the corresponding swithMap operator from Rx but from the standpoint of Kotlin coroutines looks like an amalgam of different operations that might need to be decoupled. The most basic operation seems to be this one, tentatively named as switchTransform
:
fun <T, R> Flow<T>.switchTransform(transform: suspend FlowCollector<R>.(value: T) -> Unit): Flow<R>
It would be similar to Flow.transform
but with a difference that transform
waits for transformation to complete for each element while switchTransfrom
would cancel the ongoing transformation on new incoming element and start it again.
Now, note that transform
operator can be used to implement the host of basic operators:
filter(predicate) = transform { if (predicate(it)) emit(it) }
map(block) = transform { emit(block(it)) }
flattenConcat() = transform { emitAll(it) }
flatMapConcat(block) = map(block).flattenConcat() = transform { emitAll(block(it)) }
onEach(block) = transform { block(it); emit(it) }
collect(block) = transform { block(it) }.collect()
So, if we have a basic switchTransform
operator, then it would be fitting to potentially have (at least reserve the names) the whole family of switchXxx
operators that are similar to the above above when you replace transform
with switchTransform
in their implementations.
Moreover, there is a use case (see #1269) for the collectLatest
terminal operator that should be called switchCollect
under this proposed nomenclature, because:
swithCollect(block) = switchTransform { block(it) }.collect()
However, this switchXxx
nomenclature has the following problem.
Under the proposed switchXxx
nomenclature switchMap
operator shall have the same signature as map
(operates on Flow<T>
) and should be equivalent to switchTransform { emit(block(it)) }
. But we already have experimental switchMap
operator that operates on Flow<Flow<T>>
and does switchTransform { emitAll(block(it)) }
which under the new nomenclature should have been called switchFlattenMapConcat
. It is not clear how to proceed.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:3
- Comments:9 (8 by maintainers)
@zach-klippenstein Terrific. This (tentative)
cancelOnNext
is the core primitive. So we can have:The trick is how to name it. Note, that we don’t have the concept of
OnNext
. On the other hand, it is quite a basic primitive and it looks somewhat similar toconflate
:conflate
adapts fast emitter to slow collector by skipping emitted values.cancelOnNext
adapts fast emitter to slow collector by cancelling collector.Ok. So here is a take on the
xxxLatest
nomenclature. We can havetransformLatest
that cancels ongoing transformation and the following operators that are potentially derived from it:This would also mean that we deprecate
switchMap
and rename it toflatMapLatest
.This nomenclature produces the following distinctly named variants of
flatMap
operator with different merging strategies:flatMapConcat
/flattenConcat
– concatenates all flows.flatMapLatest
/flattenLatest
– cancels ongoing flow as soon as the new one appears.flatMapMerge
/flattenMerge
– concurrently run flows and merge their results.Indeed, I like the suffix nomenclature more. I don’t like introducing a strategy enum, though. It only makes subsequent dead-code elimination in Android via R8, in Kotiln/JS, and in Kotlin/Native harder.
P.S. There is might be some confusion with
combineLatest
operator that also ends withlatest
suffix but is otherwise completely unrelated to the abovexxxLatest
family. Having thought about it a bit I don’t immediately see it as a big issue, even though it still bugs me a little.