Skip to content

Commit bed3d29

Browse files
CLOVIS-AIdkhalanskyjb
authored andcommitted
Introduce Flow.any, Flow.all, Flow.none
Fixes #4212
1 parent ec83195 commit bed3d29

File tree

4 files changed

+219
-0
lines changed

4 files changed

+219
-0
lines changed

kotlinx-coroutines-core/api/kotlinx-coroutines-core.api

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,8 @@ public abstract interface class kotlinx/coroutines/flow/FlowCollector {
980980

981981
public final class kotlinx/coroutines/flow/FlowKt {
982982
public static final field DEFAULT_CONCURRENCY_PROPERTY_NAME Ljava/lang/String;
983+
public static final fun all (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
984+
public static final fun any (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
983985
public static final fun asFlow (Ljava/lang/Iterable;)Lkotlinx/coroutines/flow/Flow;
984986
public static final fun asFlow (Ljava/util/Iterator;)Lkotlinx/coroutines/flow/Flow;
985987
public static final fun asFlow (Lkotlin/jvm/functions/Function0;)Lkotlinx/coroutines/flow/Flow;
@@ -1075,6 +1077,7 @@ public final class kotlinx/coroutines/flow/FlowKt {
10751077
public static final fun merge (Ljava/lang/Iterable;)Lkotlinx/coroutines/flow/Flow;
10761078
public static final fun merge (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
10771079
public static final fun merge ([Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
1080+
public static final fun none (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
10781081
public static final fun observeOn (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/flow/Flow;
10791082
public static final fun onCompletion (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
10801083
public static final fun onEach (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;

kotlinx-coroutines-core/api/kotlinx-coroutines-core.klib.api

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,8 @@ final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines.channels/ReceiveChannel<
997997
final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines.channels/ReceiveChannel<#A>).kotlinx.coroutines.channels/toMutableList(): kotlin.collections/MutableList<#A> // kotlinx.coroutines.channels/toMutableList|toMutableList@kotlinx.coroutines.channels.ReceiveChannel<0:0>(){0§<kotlin.Any?>}[0]
998998
final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines.channels/ReceiveChannel<#A>).kotlinx.coroutines.channels/toMutableSet(): kotlin.collections/MutableSet<#A> // kotlinx.coroutines.channels/toMutableSet|toMutableSet@kotlinx.coroutines.channels.ReceiveChannel<0:0>(){0§<kotlin.Any?>}[0]
999999
final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines.channels/ReceiveChannel<#A>).kotlinx.coroutines.channels/toSet(): kotlin.collections/Set<#A> // kotlinx.coroutines.channels/toSet|toSet@kotlinx.coroutines.channels.ReceiveChannel<0:0>(){0§<kotlin.Any?>}[0]
1000+
final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/Flow<#A>).kotlinx.coroutines.flow/all(kotlin.coroutines/SuspendFunction1<#A, kotlin/Boolean>): kotlin/Boolean // kotlinx.coroutines.flow/all|all@kotlinx.coroutines.flow.Flow<0:0>(kotlin.coroutines.SuspendFunction1<0:0,kotlin.Boolean>){0§<kotlin.Any?>}[0]
1001+
final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/Flow<#A>).kotlinx.coroutines.flow/any(kotlin.coroutines/SuspendFunction1<#A, kotlin/Boolean>): kotlin/Boolean // kotlinx.coroutines.flow/any|any@kotlinx.coroutines.flow.Flow<0:0>(kotlin.coroutines.SuspendFunction1<0:0,kotlin.Boolean>){0§<kotlin.Any?>}[0]
10001002
final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/Flow<#A>).kotlinx.coroutines.flow/collectLatest(kotlin.coroutines/SuspendFunction1<#A, kotlin/Unit>) // kotlinx.coroutines.flow/collectLatest|collectLatest@kotlinx.coroutines.flow.Flow<0:0>(kotlin.coroutines.SuspendFunction1<0:0,kotlin.Unit>){0§<kotlin.Any?>}[0]
10011003
final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/Flow<#A>).kotlinx.coroutines.flow/count(): kotlin/Int // kotlinx.coroutines.flow/count|count@kotlinx.coroutines.flow.Flow<0:0>(){0§<kotlin.Any?>}[0]
10021004
final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/Flow<#A>).kotlinx.coroutines.flow/count(kotlin.coroutines/SuspendFunction1<#A, kotlin/Boolean>): kotlin/Int // kotlinx.coroutines.flow/count|count@kotlinx.coroutines.flow.Flow<0:0>(kotlin.coroutines.SuspendFunction1<0:0,kotlin.Boolean>){0§<kotlin.Any?>}[0]
@@ -1006,6 +1008,7 @@ final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/Flow<#A>).kotlinx.c
10061008
final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/Flow<#A>).kotlinx.coroutines.flow/firstOrNull(kotlin.coroutines/SuspendFunction1<#A, kotlin/Boolean>): #A? // kotlinx.coroutines.flow/firstOrNull|firstOrNull@kotlinx.coroutines.flow.Flow<0:0>(kotlin.coroutines.SuspendFunction1<0:0,kotlin.Boolean>){0§<kotlin.Any?>}[0]
10071009
final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/Flow<#A>).kotlinx.coroutines.flow/last(): #A // kotlinx.coroutines.flow/last|last@kotlinx.coroutines.flow.Flow<0:0>(){0§<kotlin.Any?>}[0]
10081010
final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/Flow<#A>).kotlinx.coroutines.flow/lastOrNull(): #A? // kotlinx.coroutines.flow/lastOrNull|lastOrNull@kotlinx.coroutines.flow.Flow<0:0>(){0§<kotlin.Any?>}[0]
1011+
final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/Flow<#A>).kotlinx.coroutines.flow/none(kotlin.coroutines/SuspendFunction1<#A, kotlin/Boolean>): kotlin/Boolean // kotlinx.coroutines.flow/none|none@kotlinx.coroutines.flow.Flow<0:0>(kotlin.coroutines.SuspendFunction1<0:0,kotlin.Boolean>){0§<kotlin.Any?>}[0]
10091012
final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/Flow<#A>).kotlinx.coroutines.flow/single(): #A // kotlinx.coroutines.flow/single|single@kotlinx.coroutines.flow.Flow<0:0>(){0§<kotlin.Any?>}[0]
10101013
final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/Flow<#A>).kotlinx.coroutines.flow/singleOrNull(): #A? // kotlinx.coroutines.flow/singleOrNull|singleOrNull@kotlinx.coroutines.flow.Flow<0:0>(){0§<kotlin.Any?>}[0]
10111014
final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/Flow<#A>).kotlinx.coroutines.flow/stateIn(kotlinx.coroutines/CoroutineScope): kotlinx.coroutines.flow/StateFlow<#A> // kotlinx.coroutines.flow/stateIn|stateIn@kotlinx.coroutines.flow.Flow<0:0>(kotlinx.coroutines.CoroutineScope){0§<kotlin.Any?>}[0]
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
@file:JvmMultifileClass
2+
@file:JvmName("FlowKt")
3+
4+
package kotlinx.coroutines.flow
5+
6+
import kotlinx.coroutines.*
7+
import kotlin.jvm.*
8+
9+
10+
/**
11+
* A terminal operator that returns `true` and immediately cancels the flow
12+
* if at least one element matches the given [predicate].
13+
*
14+
* If the flow does not emit any elements or no element matches the predicate, the function returns `false`.
15+
*
16+
* Equivalent to `!all { !predicate(it) }` (see [Flow.all]) and `!none { predicate(it) }` (see [Flow.none]).
17+
*
18+
* Example:
19+
*
20+
* ```
21+
* val myFlow = flow {
22+
* repeat(10) {
23+
* emit(it)
24+
* }
25+
* throw RuntimeException("You still didn't find the required number? I gave you ten!")
26+
* }
27+
* println(myFlow.any { it > 5 }) // true
28+
* println(flowOf(1, 2, 3).any { it > 5 }) // false
29+
* ```
30+
*
31+
* @see Iterable.any
32+
* @see Sequence.any
33+
*/
34+
public suspend fun <T> Flow<T>.any(predicate: suspend (T) -> Boolean): Boolean {
35+
var found = false
36+
collectWhile {
37+
val satisfies = predicate(it)
38+
if (satisfies) found = true
39+
!satisfies
40+
}
41+
return found
42+
}
43+
44+
/**
45+
* A terminal operator that returns `true` if all elements match the given [predicate],
46+
* or returns `false` and cancels the flow as soon as the first element not matching the predicate is encountered.
47+
*
48+
* If the flow terminates without emitting any elements, the function returns `true` because there
49+
* are no elements in it that *do not* match the predicate.
50+
* See a more detailed explanation of this logic concept in the
51+
* ["Vacuous truth"](https://en.wikipedia.org/wiki/Vacuous_truth) article.
52+
*
53+
* Equivalent to `!any { !predicate(it) }` (see [Flow.any]) and `none { !predicate(it) }` (see [Flow.none]).
54+
*
55+
* Example:
56+
*
57+
* ```
58+
* val myFlow = flow {
59+
* repeat(10) {
60+
* emit(it)
61+
* }
62+
* throw RuntimeException("You still didn't find the required number? I gave you ten!")
63+
* }
64+
* println(myFlow.all { it <= 5 }) // false
65+
* println(flowOf(1, 2, 3).all { it <= 5 }) // true
66+
* ```
67+
*
68+
* @see Iterable.all
69+
* @see Sequence.all
70+
*/
71+
public suspend fun <T> Flow<T>.all(predicate: suspend (T) -> Boolean): Boolean {
72+
var foundCounterExample = false
73+
collectWhile {
74+
val satisfies = predicate(it)
75+
if (!satisfies) foundCounterExample = true
76+
satisfies
77+
}
78+
return !foundCounterExample
79+
}
80+
81+
/**
82+
* A terminal operator that returns `true` if no elements match the given [predicate],
83+
* or returns `false` and cancels the flow as soon as the first element matching the predicate is encountered.
84+
*
85+
* If the flow terminates without emitting any elements, the function returns `true` because there
86+
* are no elements in it that match the predicate.
87+
* See a more detailed explanation of this logic concept in the
88+
* ["Vacuous truth"](https://en.wikipedia.org/wiki/Vacuous_truth) article.
89+
*
90+
* Equivalent to `!any(predicate)` (see [Flow.any]) and `all { !predicate(it) }` (see [Flow.all]).
91+
*
92+
* Example:
93+
* ```
94+
* val myFlow = flow {
95+
* repeat(10) {
96+
* emit(it)
97+
* }
98+
* throw RuntimeException("You still didn't find the required number? I gave you ten!")
99+
* }
100+
* println(myFlow.none { it > 5 }) // false
101+
* println(flowOf(1, 2, 3).none { it > 5 }) // true
102+
* ```
103+
*
104+
* @see Iterable.none
105+
* @see Sequence.none
106+
*/
107+
public suspend fun <T> Flow<T>.none(predicate: suspend (T) -> Boolean): Boolean = !any(predicate)
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package kotlinx.coroutines.flow
2+
3+
import kotlinx.coroutines.testing.*
4+
import kotlin.test.*
5+
6+
class BooleanTerminationTest : TestBase() {
7+
@Test
8+
fun testAnyNominal() = runTest {
9+
val flow = flow {
10+
emit(1)
11+
emit(2)
12+
}
13+
14+
assertTrue(flow.any { it > 0 })
15+
assertTrue(flow.any { it % 2 == 0 })
16+
assertFalse(flow.any { it > 5 })
17+
}
18+
19+
@Test
20+
fun testAnyEmpty() = runTest {
21+
assertFalse(emptyFlow<Int>().any { it > 0 })
22+
}
23+
24+
@Test
25+
fun testAnyInfinite() = runTest {
26+
assertTrue(flow { while (true) { emit(5) } }.any { it == 5 })
27+
}
28+
29+
@Test
30+
fun testAnyShortCircuit() = runTest {
31+
assertTrue(flow {
32+
emit(1)
33+
emit(2)
34+
expectUnreached()
35+
}.any {
36+
it == 2
37+
})
38+
}
39+
40+
@Test
41+
fun testAllNominal() = runTest {
42+
val flow = flow {
43+
emit(1)
44+
emit(2)
45+
}
46+
47+
assertTrue(flow.all { it > 0 })
48+
assertFalse(flow.all { it % 2 == 0 })
49+
assertFalse(flow.all { it > 5 })
50+
}
51+
52+
@Test
53+
fun testAllEmpty() = runTest {
54+
assertTrue(emptyFlow<Int>().all { it > 0 })
55+
}
56+
57+
@Test
58+
fun testAllInfinite() = runTest {
59+
assertFalse(flow { while (true) { emit(5) } }.all { it == 0 })
60+
}
61+
62+
@Test
63+
fun testAllShortCircuit() = runTest {
64+
assertFalse(flow {
65+
emit(1)
66+
emit(2)
67+
expectUnreached()
68+
}.all {
69+
it <= 1
70+
})
71+
}
72+
73+
@Test
74+
fun testNoneNominal() = runTest {
75+
val flow = flow {
76+
emit(1)
77+
emit(2)
78+
}
79+
80+
assertFalse(flow.none { it > 0 })
81+
assertFalse(flow.none { it % 2 == 0 })
82+
assertTrue(flow.none { it > 5 })
83+
}
84+
85+
@Test
86+
fun testNoneEmpty() = runTest {
87+
assertTrue(emptyFlow<Int>().none { it > 0 })
88+
}
89+
90+
@Test
91+
fun testNoneInfinite() = runTest {
92+
assertFalse(flow { while (true) { emit(5) } }.none { it == 5 })
93+
}
94+
95+
@Test
96+
fun testNoneShortCircuit() = runTest {
97+
assertFalse(flow {
98+
emit(1)
99+
emit(2)
100+
expectUnreached()
101+
}.none {
102+
it == 2
103+
})
104+
}
105+
106+
}

0 commit comments

Comments
 (0)