Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flow.transformWhile operator #2065

Closed
elizarov opened this issue May 27, 2020 · 0 comments
Closed

Flow.transformWhile operator #2065

elizarov opened this issue May 27, 2020 · 0 comments
Assignees
Labels

Comments

@elizarov
Copy link
Contributor

elizarov commented May 27, 2020

Kotlin flow provides Flow<T>.takeWhile { ... } operator that emits values while the predicate returns true and then completes the flow without emitting the last value (where the predicate returned true). Its behavior, in this respect, is consistent with dropWhile operator.

In light of the design of shared flows (see #2034) that never complete by themselves there is a use-case where the matching value needs to be emitted, too. For example, consider a shared flow of download progress messages that ends with a message that download was done. We want to use this shared flow in a way that it completes when the download is done but also emits this last value because it also contains additional information. See also the similar request in #1816 (comment)

Naming challenges

Both RxJava and Project Reactor provide takeUntil operators for this use-case so that you can write takeUntil { it.isTheLastOne() }. However, the word "Until" has a completely different meaning in Koltin's core libraries. It is used to indicate a range up to, but excluding the last element. Moreover, Kotlin (the language) does not have any kind of repeat ... until predicate loop that can serve as inspiration. Kotlin consistently uses while for that purpose in both pre-condition while(predicate) { ... } and post-condition do { ... } while(predicate) forms. So takeUntil name is a no-go for Kotlin.

RxJS provides takeWhile(predicate, inclusive = true) operator for this use-case. However, we feel that accepting boolean here provides misplaced flexibility, since in all the uses it is likely going to be just constant and dedicated version of takeWhile operator would have worked better.

We cannot find a Kotliny name for this kind of use-case-specific operator.

Flow-style flexiblity

Flow-truncation operators (like takeWhile) are quite difficult to write correctly, so it would be wrong to leave the use-case of truncating a flow and emitting the last element to be unmet. However, we do have a similar problem in other places of Kotlin Flow design where some operators (like mapLatest) are too inflexible for some specific use-cass. We are aiming to minimize the number of operators in the library, and we want to provide both a flexible and easy-to-use operator. Hence we have transformXxx operators (like transformLatest) that accept a block of code that can make a decision of what and when to emit.

So, the proposal is to take the same road here and provide transformWhile operator:

fun <T, R> Flow<T>.transformWhile(
    transform: suspend FlowCollector<R>.(value: T) -> Boolean
): Flow<R>

The transformWhile operator generalizes takeWhile just like transform operator generalizes both filter/map and transformLatest generalizes mapLatest:

takeWhile(predicate) = 
    transformWhile { 
        if (predicate) { emit(it); true }
        else false
    }

Using this transformWhile operator one can complete a flow on the last value and emit it, too, by writing:

flow.transformWhile { emit(it); it.isNotTheLastOne() }
@elizarov elizarov added the flow label May 27, 2020
elizarov added a commit that referenced this issue May 27, 2020
Also, all flow-truncating operators are refactored via a common internal collectWhile operator that properly uses AbortFlowException and checks for its ownership, so that we don't have to look for bugs in interactions between all those operators (and zip, too, which is also flow-truncating).

Fixes #2065
@elizarov elizarov self-assigned this May 27, 2020
elizarov added a commit that referenced this issue Jun 22, 2020
Also, all flow-truncating operators are refactored via a common internal collectWhile operator that properly uses AbortFlowException and checks for its ownership, so that we don't have to look for bugs in interactions between all those operators (and zip, too, which is also flow-truncating).

Fixes #2065
@elizarov elizarov mentioned this issue Jul 16, 2020
1 task
recheej pushed a commit to recheej/kotlinx.coroutines that referenced this issue Dec 28, 2020
Also, most flow-truncating operators are refactored via a common internal collectWhile operator that properly uses AbortFlowException and checks for its ownership, so that we don't have to look for bugs in interactions between all those operators (and zip, too, which is also flow-truncating). But `take` operator still users a custom highly-tuned implementation.

Fixes Kotlin#2065

Co-authored-by: EdwarDDay <4127904+EdwarDDay@users.noreply.github.com>
Co-authored-by: Louis CAD <louis.cognault@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant