From 59b46f00fa35e0e3ad193469787ffc09abc551cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Mon, 14 Oct 2024 23:22:50 +0200 Subject: [PATCH 1/8] Introduce Flow.any, Flow.all, Flow.none See #4212 --- .../common/src/flow/operators/Transform.kt | 43 ++++++++++++++ .../flow/operators/BooleanTerminationTest.kt | 58 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 kotlinx-coroutines-core/common/test/flow/operators/BooleanTerminationTest.kt diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt index f3c9be1c7e..97a0cdcece 100644 --- a/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt +++ b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt @@ -164,3 +164,46 @@ public fun Flow.chunked(size: Int): Flow> { result?.let { emit(it) } } } + +/** + * Returns `true` if at least one element matches the given [predicate]. + * + * This operation is *terminal*. + * + * @see Iterable.any + * @see Sequence.any + */ +@ExperimentalCoroutinesApi +public suspend fun Flow.any(predicate: suspend (T) -> Boolean): Boolean = this + .filter { predicate(it) } + .map { true } + .onEmpty { emit(false) } + .first() + +/** + * Returns `true` if all elements match the given [predicate]. + * + * This operation is *terminal*. + * + * Note that if the flow terminates without emitting any elements, the function returns `true` because there + * are no elements in it that *do not* match the predicate. + * See a more detailed explanation of this logic concept in ["Vacuous truth"](https://en.wikipedia.org/wiki/Vacuous_truth) article. + * + * @see Iterable.all + * @see Sequence.all + */ +@ExperimentalCoroutinesApi +public suspend fun Flow.all(predicate: suspend (T) -> Boolean): Boolean = + count { !predicate(it) } == 0 + +/** + * Returns `true` if none of the elements match the given [predicate]. + * + * This operation is *terminal*. + * + * @see Iterable.none + * @see Sequence.none + */ +@ExperimentalCoroutinesApi +public suspend fun Flow.none(predicate: suspend (T) -> Boolean): Boolean = + count { predicate(it) } == 0 diff --git a/kotlinx-coroutines-core/common/test/flow/operators/BooleanTerminationTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/BooleanTerminationTest.kt new file mode 100644 index 0000000000..acfb86c3d0 --- /dev/null +++ b/kotlinx-coroutines-core/common/test/flow/operators/BooleanTerminationTest.kt @@ -0,0 +1,58 @@ +package kotlinx.coroutines.flow + +import kotlinx.coroutines.testing.* +import kotlin.test.* + +class BooleanTerminationTest : TestBase() { + @Test + fun testAnyNominal() = runTest { + val flow = flow { + emit(1) + emit(2) + } + + assertTrue(flow.any { it > 0 }) + assertTrue(flow.any { it % 2 == 0 }) + assertFalse(flow.any { it > 5 }) + } + + @Test + fun testAnyEmpty() = runTest { + assertFalse(emptyFlow().any { it > 0 }) + } + + @Test + fun testAllNominal() = runTest { + val flow = flow { + emit(1) + emit(2) + } + + assertTrue(flow.all { it > 0 }) + assertFalse(flow.all { it % 2 == 0 }) + assertFalse(flow.all { it > 5 }) + } + + @Test + fun testAllEmpty() = runTest { + assertTrue(emptyFlow().all { it > 0 }) + } + + @Test + fun testNoneNominal() = runTest { + val flow = flow { + emit(1) + emit(2) + } + + assertFalse(flow.none { it > 0 }) + assertFalse(flow.none { it % 2 == 0 }) + assertTrue(flow.none { it > 5 }) + } + + @Test + fun testNoneEmpty() = runTest { + assertTrue(emptyFlow().none { it > 0 }) + } + +} From e9eff4517892b3b9867b519b4a6891c538212851 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 19 Dec 2024 12:22:16 +0100 Subject: [PATCH 2/8] Move the implementation to flow/terminal --- .../common/src/flow/operators/Transform.kt | 43 ---------------- .../common/src/flow/terminal/Logic.kt | 51 +++++++++++++++++++ 2 files changed, 51 insertions(+), 43 deletions(-) create mode 100644 kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt index 97a0cdcece..f3c9be1c7e 100644 --- a/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt +++ b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt @@ -164,46 +164,3 @@ public fun Flow.chunked(size: Int): Flow> { result?.let { emit(it) } } } - -/** - * Returns `true` if at least one element matches the given [predicate]. - * - * This operation is *terminal*. - * - * @see Iterable.any - * @see Sequence.any - */ -@ExperimentalCoroutinesApi -public suspend fun Flow.any(predicate: suspend (T) -> Boolean): Boolean = this - .filter { predicate(it) } - .map { true } - .onEmpty { emit(false) } - .first() - -/** - * Returns `true` if all elements match the given [predicate]. - * - * This operation is *terminal*. - * - * Note that if the flow terminates without emitting any elements, the function returns `true` because there - * are no elements in it that *do not* match the predicate. - * See a more detailed explanation of this logic concept in ["Vacuous truth"](https://en.wikipedia.org/wiki/Vacuous_truth) article. - * - * @see Iterable.all - * @see Sequence.all - */ -@ExperimentalCoroutinesApi -public suspend fun Flow.all(predicate: suspend (T) -> Boolean): Boolean = - count { !predicate(it) } == 0 - -/** - * Returns `true` if none of the elements match the given [predicate]. - * - * This operation is *terminal*. - * - * @see Iterable.none - * @see Sequence.none - */ -@ExperimentalCoroutinesApi -public suspend fun Flow.none(predicate: suspend (T) -> Boolean): Boolean = - count { predicate(it) } == 0 diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt new file mode 100644 index 0000000000..b6be375119 --- /dev/null +++ b/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt @@ -0,0 +1,51 @@ +@file:JvmMultifileClass +@file:JvmName("FlowKt") + +package kotlinx.coroutines.flow + +import kotlinx.coroutines.* +import kotlin.jvm.* + + +/** + * Returns `true` if at least one element matches the given [predicate]. + * + * This operation is *terminal*. + * + * @see Iterable.any + * @see Sequence.any + */ +@ExperimentalCoroutinesApi +public suspend fun Flow.any(predicate: suspend (T) -> Boolean): Boolean = this + .filter { predicate(it) } + .map { true } + .onEmpty { emit(false) } + .first() + +/** + * Returns `true` if all elements match the given [predicate]. + * + * This operation is *terminal*. + * + * Note that if the flow terminates without emitting any elements, the function returns `true` because there + * are no elements in it that *do not* match the predicate. + * See a more detailed explanation of this logic concept in ["Vacuous truth"](https://en.wikipedia.org/wiki/Vacuous_truth) article. + * + * @see Iterable.all + * @see Sequence.all + */ +@ExperimentalCoroutinesApi +public suspend fun Flow.all(predicate: suspend (T) -> Boolean): Boolean = + count { !predicate(it) } == 0 + +/** + * Returns `true` if none of the elements match the given [predicate]. + * + * This operation is *terminal*. + * + * @see Iterable.none + * @see Sequence.none + */ +@ExperimentalCoroutinesApi +public suspend fun Flow.none(predicate: suspend (T) -> Boolean): Boolean = + count { predicate(it) } == 0 From c7369597023ea8b598da56750e8b9ddd64ea8739 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 19 Dec 2024 12:26:54 +0100 Subject: [PATCH 3/8] Rewrite the implementations to be lazy --- .../common/src/flow/terminal/Logic.kt | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt index b6be375119..50cae5b5e1 100644 --- a/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt +++ b/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt @@ -16,11 +16,15 @@ import kotlin.jvm.* * @see Sequence.any */ @ExperimentalCoroutinesApi -public suspend fun Flow.any(predicate: suspend (T) -> Boolean): Boolean = this - .filter { predicate(it) } - .map { true } - .onEmpty { emit(false) } - .first() +public suspend fun Flow.any(predicate: suspend (T) -> Boolean): Boolean { + var found = false + collectWhile { + predicate(it).also { satisfies -> + if (satisfies) found = true + }.let(Boolean::not) + } + return found +} /** * Returns `true` if all elements match the given [predicate]. @@ -35,8 +39,15 @@ public suspend fun Flow.any(predicate: suspend (T) -> Boolean): Boolean = * @see Sequence.all */ @ExperimentalCoroutinesApi -public suspend fun Flow.all(predicate: suspend (T) -> Boolean): Boolean = - count { !predicate(it) } == 0 +public suspend fun Flow.all(predicate: suspend (T) -> Boolean): Boolean { + var foundCounterExample = false + collectWhile { + predicate(it).also { satisfies -> + if (!satisfies) foundCounterExample = true + } + } + return !foundCounterExample +} /** * Returns `true` if none of the elements match the given [predicate]. @@ -47,5 +58,4 @@ public suspend fun Flow.all(predicate: suspend (T) -> Boolean): Boolean = * @see Sequence.none */ @ExperimentalCoroutinesApi -public suspend fun Flow.none(predicate: suspend (T) -> Boolean): Boolean = - count { predicate(it) } == 0 +public suspend fun Flow.none(predicate: suspend (T) -> Boolean): Boolean = !any(predicate) From 8b89a55c80a0406186825f252755e0fb223dfaa5 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 19 Dec 2024 12:32:35 +0100 Subject: [PATCH 4/8] Add tests for short-circuiting --- .../flow/operators/BooleanTerminationTest.kt | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/kotlinx-coroutines-core/common/test/flow/operators/BooleanTerminationTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/BooleanTerminationTest.kt index acfb86c3d0..3087c78f67 100644 --- a/kotlinx-coroutines-core/common/test/flow/operators/BooleanTerminationTest.kt +++ b/kotlinx-coroutines-core/common/test/flow/operators/BooleanTerminationTest.kt @@ -21,6 +21,22 @@ class BooleanTerminationTest : TestBase() { assertFalse(emptyFlow().any { it > 0 }) } + @Test + fun testAnyInfinite() = runTest { + assertTrue(flow { while (true) { emit(5) } }.any { it == 5 }) + } + + @Test + fun testAnyShortCircuit() = runTest { + assertTrue(flow { + emit(1) + emit(2) + expectUnreached() + }.any { + it == 2 + }) + } + @Test fun testAllNominal() = runTest { val flow = flow { @@ -38,6 +54,22 @@ class BooleanTerminationTest : TestBase() { assertTrue(emptyFlow().all { it > 0 }) } + @Test + fun testAllInfinite() = runTest { + assertFalse(flow { while (true) { emit(5) } }.all { it == 0 }) + } + + @Test + fun testAllShortCircuit() = runTest { + assertFalse(flow { + emit(1) + emit(2) + expectUnreached() + }.all { + it <= 1 + }) + } + @Test fun testNoneNominal() = runTest { val flow = flow { @@ -55,4 +87,20 @@ class BooleanTerminationTest : TestBase() { assertTrue(emptyFlow().none { it > 0 }) } + @Test + fun testNoneInfinite() = runTest { + assertFalse(flow { while (true) { emit(5) } }.none { it == 5 }) + } + + @Test + fun testNoneShortCircuit() = runTest { + assertFalse(flow { + emit(1) + emit(2) + expectUnreached() + }.none { + it == 2 + }) + } + } From ca78c68cce33c75518a7e87e5d2c764d5e1d8e21 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 19 Dec 2024 12:43:17 +0100 Subject: [PATCH 5/8] Update the documentation --- .../common/src/flow/terminal/Logic.kt | 67 ++++++++++++++++--- 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt index 50cae5b5e1..41f06948b0 100644 --- a/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt +++ b/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt @@ -8,9 +8,25 @@ import kotlin.jvm.* /** - * Returns `true` if at least one element matches the given [predicate]. + * A terminal operator that returns `true` and immediately cancels the flow + * if at least one element matches the given [predicate]. * - * This operation is *terminal*. + * If the flow does not emit any elements or no element matches the predicate, the function returns `false`. + * + * Equivalent to `!all { !predicate(it) }` (see [Flow.all]) and `!none { predicate(it) }` (see [Flow.none]). + * + * Example: + * + * ``` + * val myFlow = flow { + * repeat(10) { + * emit(it) + * } + * throw RuntimeException("You still didn't find the required number? I gave you ten!") + * } + * println(myFlow.any { it > 5 }) // true + * println(flowOf(1, 2, 3).any { it > 5 }) // false + * ``` * * @see Iterable.any * @see Sequence.any @@ -27,13 +43,28 @@ public suspend fun Flow.any(predicate: suspend (T) -> Boolean): Boolean { } /** - * Returns `true` if all elements match the given [predicate]. + * A terminal operator that returns `true` if all elements match the given [predicate], + * or returns `false` and cancels the flow as soon as the first element not matching the predicate is encountered. * - * This operation is *terminal*. - * - * Note that if the flow terminates without emitting any elements, the function returns `true` because there + * If the flow terminates without emitting any elements, the function returns `true` because there * are no elements in it that *do not* match the predicate. - * See a more detailed explanation of this logic concept in ["Vacuous truth"](https://en.wikipedia.org/wiki/Vacuous_truth) article. + * See a more detailed explanation of this logic concept in the + * ["Vacuous truth"](https://en.wikipedia.org/wiki/Vacuous_truth) article. + * + * Equivalent to `!any { !predicate(it) }` (see [Flow.any]) and `none { !predicate(it) }` (see [Flow.none]). + * + * Example: + * + * ``` + * val myFlow = flow { + * repeat(10) { + * emit(it) + * } + * throw RuntimeException("You still didn't find the required number? I gave you ten!") + * } + * println(myFlow.all { it <= 5 }) // false + * println(flowOf(1, 2, 3).all { it <= 5 }) // true + * ``` * * @see Iterable.all * @see Sequence.all @@ -50,9 +81,27 @@ public suspend fun Flow.all(predicate: suspend (T) -> Boolean): Boolean { } /** - * Returns `true` if none of the elements match the given [predicate]. + * A terminal operator that returns `true` if no elements match the given [predicate], + * or returns `false` and cancels the flow as soon as the first element matching the predicate is encountered. + * + * If the flow terminates without emitting any elements, the function returns `true` because there + * are no elements in it that match the predicate. + * See a more detailed explanation of this logic concept in the + * ["Vacuous truth"](https://en.wikipedia.org/wiki/Vacuous_truth) article. + * + * Equivalent to `!any(predicate)` (see [Flow.any]) and `all { !predicate(it) }` (see [Flow.all]). * - * This operation is *terminal*. + * Example: + * ``` + * val myFlow = flow { + * repeat(10) { + * emit(it) + * } + * throw RuntimeException("You still didn't find the required number? I gave you ten!") + * } + * println(myFlow.none { it > 5 }) // false + * println(flowOf(1, 2, 3).none { it > 5 }) // true + * ``` * * @see Iterable.none * @see Sequence.none From 9af0bd965bd7f7ae9e89802a9ab096fcd8efac97 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:04:18 +0100 Subject: [PATCH 6/8] Don't mark the new operators as experimental --- kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt index 41f06948b0..f7312a748a 100644 --- a/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt +++ b/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt @@ -31,7 +31,6 @@ import kotlin.jvm.* * @see Iterable.any * @see Sequence.any */ -@ExperimentalCoroutinesApi public suspend fun Flow.any(predicate: suspend (T) -> Boolean): Boolean { var found = false collectWhile { @@ -69,7 +68,6 @@ public suspend fun Flow.any(predicate: suspend (T) -> Boolean): Boolean { * @see Iterable.all * @see Sequence.all */ -@ExperimentalCoroutinesApi public suspend fun Flow.all(predicate: suspend (T) -> Boolean): Boolean { var foundCounterExample = false collectWhile { @@ -106,5 +104,4 @@ public suspend fun Flow.all(predicate: suspend (T) -> Boolean): Boolean { * @see Iterable.none * @see Sequence.none */ -@ExperimentalCoroutinesApi public suspend fun Flow.none(predicate: suspend (T) -> Boolean): Boolean = !any(predicate) From 9dbbf418923b619ff7a6d5bea4fac4c1ded5acd0 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 19 Dec 2024 14:11:55 +0100 Subject: [PATCH 7/8] Update the API dump --- kotlinx-coroutines-core/api/kotlinx-coroutines-core.api | 3 +++ kotlinx-coroutines-core/api/kotlinx-coroutines-core.klib.api | 3 +++ 2 files changed, 6 insertions(+) diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index 189a0f3544..6d75746e6b 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -980,6 +980,8 @@ public abstract interface class kotlinx/coroutines/flow/FlowCollector { public final class kotlinx/coroutines/flow/FlowKt { public static final field DEFAULT_CONCURRENCY_PROPERTY_NAME Ljava/lang/String; + public static final fun all (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun any (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun asFlow (Ljava/lang/Iterable;)Lkotlinx/coroutines/flow/Flow; public static final fun asFlow (Ljava/util/Iterator;)Lkotlinx/coroutines/flow/Flow; public static final fun asFlow (Lkotlin/jvm/functions/Function0;)Lkotlinx/coroutines/flow/Flow; @@ -1075,6 +1077,7 @@ public final class kotlinx/coroutines/flow/FlowKt { public static final fun merge (Ljava/lang/Iterable;)Lkotlinx/coroutines/flow/Flow; public static final fun merge (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow; public static final fun merge ([Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow; + public static final fun none (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun observeOn (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/flow/Flow; public static final fun onCompletion (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow; public static final fun onEach (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow; diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.klib.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.klib.api index f419598f9b..a27e3154af 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.klib.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.klib.api @@ -997,6 +997,8 @@ final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines.channels/ReceiveChannel< 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§}[0] 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§}[0] 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§}[0] +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§}[0] +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§}[0] 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§}[0] 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§}[0] 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§}[0] @@ -1006,6 +1008,7 @@ final suspend fun <#A: kotlin/Any?> (kotlinx.coroutines.flow/Flow<#A>).kotlinx.c 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§}[0] 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§}[0] 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§}[0] +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§}[0] 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§}[0] 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§}[0] 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§}[0] From ff093ff20a4b4d72c43e63769b0b717b91009cb1 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:29:25 +0100 Subject: [PATCH 8/8] Apply suggestions from code review Co-authored-by: Jake Wharton --- .../common/src/flow/terminal/Logic.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt index f7312a748a..6d1cd6fee9 100644 --- a/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt +++ b/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt @@ -34,9 +34,9 @@ import kotlin.jvm.* public suspend fun Flow.any(predicate: suspend (T) -> Boolean): Boolean { var found = false collectWhile { - predicate(it).also { satisfies -> - if (satisfies) found = true - }.let(Boolean::not) + val satisfies = predicate(it) + if (satisfies) found = true + !satisfies } return found } @@ -71,9 +71,9 @@ public suspend fun Flow.any(predicate: suspend (T) -> Boolean): Boolean { public suspend fun Flow.all(predicate: suspend (T) -> Boolean): Boolean { var foundCounterExample = false collectWhile { - predicate(it).also { satisfies -> - if (!satisfies) foundCounterExample = true - } + val satisfies = predicate(it) + if (!satisfies) foundCounterExample = true + satisfies } return !foundCounterExample }