Combine two state flows into new state flow - kotlin

I have two state flows. Is it possible to combine them and get new state flow? Logically it should be possible because both state flows have initials values, but as I see combine function returns just Flow and not StateFlow.

You could use the combine operator, and then use the stateIn function for the Flow that results from that.
From the stateIn documentation in the kotlinx coroutines repository:
The stateIn function "Converts a cold Flow into a hot StateFlow that is started in the given coroutine scope, sharing the most recently emitted value from a single running instance of the upstream flow with multiple downstream subscribers."
It's signature, as of the time of writing this, is:
fun <T> Flow<T>.stateIn(
scope: CoroutineScope,
started: SharingStarted,
initialValue: T
): StateFlow<T> (source)
So you should be able to do whatever transformations you need to your Flows, including combining them, and then ultimately use stateIn to convert them back to a StateFlow.
It could look something like this (perhaps creating a Scrabble game point calculator):
val wordFlow = MutableStateFlow("Hi")
val pointFlow = MutableStateFlow(5)
val stateString = wordFlow.combine(pointFlow) { word, points ->
"$word is worth $points points"
}.stateIn(viewModelScope, SharingStarted.Eagerly, "Default is worth 0 points")
stateString would be of type StateFlow<String>, and you have successfully combined the two other StateFlows into one StateFlow.

So far I created function:
fun <T1, T2, R> combineState(
flow1: StateFlow<T1>,
flow2: StateFlow<T2>,
scope: CoroutineScope = GlobalScope,
sharingStarted: SharingStarted = SharingStarted.Eagerly,
transform: (T1, T2) -> R
): StateFlow<R> = combine(flow1, flow2) {
o1, o2 -> transform.invoke(o1, o2)
}.stateIn(scope, sharingStarted, transform.invoke(flow1.value, flow2.value))

Above mentioned solutions are using stateIn() with GlobalScope and policy as Eagerly which means these StateFlows will never stop being observed once created which can lead to the issues.
I already have mentioned details in this blog. Instead, create a separate class which derives a new StateFlow:
private class TransformedStateFlow<T>(
private val getValue: () -> T,
private val flow: Flow<T>
) : StateFlow<T> {
override val replayCache: List<T> get() = listOf(value)
override val value: T get() = getValue()
override suspend fun collect(collector: FlowCollector<T>): Nothing =
coroutineScope { flow.stateIn(this).collect(collector) }
}
/**
* Returns [StateFlow] from [flow] having initial value from calculation of [getValue]
*/
fun <T> stateFlow(
getValue: () -> T,
flow: Flow<T>
): StateFlow<T> = TransformedStateFlow(getValue, flow)
/**
* Combines all [stateFlows] and transforms them into another [StateFlow] with [transform]
*/
inline fun <reified T, R> combineStates(
vararg stateFlows: StateFlow<T>,
crossinline transform: (Array<T>) -> R
): StateFlow<R> = stateFlow(
getValue = { transform(stateFlows.map { it.value }.toTypedArray()) },
flow = combine(*stateFlows) { transform(it) }
)
/**
* Variant of [combineStates] for combining 3 state flows
*/
inline fun <reified T1, reified T2, reified T3, R> combineStates(
flow1: StateFlow<T1>,
flow2: StateFlow<T2>,
flow3: StateFlow<T3>,
crossinline transform: (T1, T2, T3) -> R
) = combineStates(flow1, flow2, flow3) { (t1, t2, t3) ->
transform(
t1 as T1,
t2 as T2,
t3 as T3
)
}
// Other variants for combining N StateFlows
After this, you can implement it in your use case. For example:
private val isLoading = MutableStateFlow(false)
private val loggedInUser = MutableStateFlow<User?>(null)
private val error = MutableStateFlow<String?>(null)
// Combining these states to form a LoginState
val state: StateFlow<LoginState> = combineStates(isLoading, loggedInUser, error) { loading, user, errorMessage ->
LoginState(loading, user, errorMessage)
}
This approach is safe than other mentioned approaches since it'll only listen to the StateFlow updates when actually it's being collected (inside consumer's Coroutine scope)

Similar solution to #Nikola Despotoski, but in a form of an extension function
/**
* Combines two [StateFlow]s into a single [StateFlow]
*/
fun <T1, T2, R> StateFlow<T1>.combineState(
flow2: StateFlow<T2>,
scope: CoroutineScope = GlobalScope,
sharingStarted: SharingStarted = SharingStarted.Eagerly,
transform: (T1, T2) -> R
): StateFlow<R> = combine(this, flow2) { o1, o2 -> transform.invoke(o1, o2) }
.stateIn(scope, sharingStarted, transform.invoke(this.value, flow2.value))

Combine n state flows
#Suppress("CHANGING_ARGUMENTS_EXECUTION_ORDER_FOR_NAMED_VARARGS")
inline fun <reified T, R> combineStateFlow(
vararg flows: StateFlow<T>,
scope: CoroutineScope = GlobalScope,
sharingStarted: SharingStarted = SharingStarted.Eagerly,
crossinline transform: (Array<T>) -> R
): StateFlow<R> = combine(flows = flows) {
transform.invoke(it)
}.stateIn(
scope = scope,
started = sharingStarted,
initialValue = transform.invoke(flows.map {
it.value
}.toTypedArray())
)
Using:
data class A(val a: String)
data class B(val b: Int)
private val test1 = MutableStateFlow(A("a"))
private val test2 = MutableStateFlow(B(2))
#Suppress("CHANGING_ARGUMENTS_EXECUTION_ORDER_FOR_NAMED_VARARGS")
private val _isValidForm = combineStateFlow(
flows = arrayOf(test1, test2),
scope = viewModelScope
) { combinedFlows: Array<Any> ->
combinedFlows.map {
val doSomething = when (it) {
is A -> true
is B -> false
else -> false
}
}
}
Gist

Use combine operator, it takes two flows and a transformation function to combine the results from both flows.
val int = MutableStateFlow(2)
val double = MutableStateFlow(1.8)
int.combine(double){ i, d ->
i + d
}.collect(::println)

Related

How can I generalize the arity of rxjava2 Zip function (from Single/Observable) to n Nullable arguments without lose its types?

Two Main Problems to solve:
1) Type check is lost
Using the array argument Single.zip() version I lose the strongly typed arguments.
2) Source argument Cannot be Nullable
I cannot send nullable source values as argument of Single.zip() function
3) I want an alternative to the method taking an Object[] not typed:
4) I don't want Mutable Objects, I don't want the use of var in my class, i want to use val
public static <T, R> Single<R> zipArray(Function<? super Object[], ? extends R> zipper, SingleSource<? extends T>... sources) ...
In haskell, there is a question related How can I implement generalized "zipn" and "unzipn" in Haskell?:
And in haskell I can achieve this with applicative functors:
f <$> a1 <*> a2 <*> a3 <*> a4 <*> a5 <*> a6 <*> a7 <*> a8 <*> a9 <*> a10 <*> a11
being f :: Int -> Int -> Int -> Int -> Int -> Int -> Int -> String -> String -> String -> Int
and a1 .. a11 values corresponding each type
There is a list of similar functions in the library:
With two arguments:
public static <T1, T2, R> Single<R> zip(SingleSource<? extends T1> source1, SingleSource<? extends T2> source2,BiFunction<? super T1, ? super T2, ? extends R> zipper) {
ObjectHelper.requireNonNull(source1, "source1 is null");
ObjectHelper.requireNonNull(source2, "source2 is null");
return zipArray(Functions.toFunction(zipper), source1, source2);
}
with three:
public static <T1, T2, T3, R> Single<R> zip(
SingleSource<? extends T1> source1, SingleSource<? extends T2> source2,
SingleSource<? extends T3> source3,
Function3<? super T1, ? super T2, ? super T3, ? extends R> zipper)
And so on...
In all those cases, is just fine, because each argument is typed. But there is a limitation until 9 Single sources
In our project, we needed more sources, because we have a lot of services that we want to reach async (in our case was 11 arguments).
But the issue is the arguments lose their strong types, and worse, some of them could be Nullable
For example we wanted to solve this use case:
//Given
val bothSubscribed = CountDownLatch(2) // Change this value to 0 to run the test faster
val subscribeThreadsStillRunning = CountDownLatch(1) // Change this value to 0 to run the test faster
val service = { s1: String,
s2: Int,
s3: String?,
s4: Int,
s5: String,
s6: String,
s7: String,
s8: String,
s9: String,
s10: String?,
s11: String ->
val result =
listOf(s1, "$s2", s3 ?: "none", "$s4", s5, s6, s7, s8, s9, s10 ?: "none", s11).joinToString(separator = ";")
Single.just("Values:$result")
}
val createSingle = { value: String ->
Observable
.create<String> { emitter ->
println("Parallel subscribe $value on ${Thread.currentThread().name}")
bothSubscribed.countDown()
subscribeThreadsStillRunning.await(20, TimeUnit.SECONDS)
emitter.onNext(value)
emitter.onComplete()
}
.singleOrError()
.subscribeOn(io())
}
val s1 = createSingle("v1")
val s2 = Single.just(2)
val s3 = null
val s4 = Single.just(4)
val s5 = createSingle("v5")
val s6 = createSingle("v6")
val s7 = createSingle("v7")
val s8 = createSingle("v8")
val s9 = createSingle("v9")
val s10 = null
val s11 = createSingle("v11")
//When
val result = Single.zipArray(
listOf(
s1,
s2,
s3,
s4,
s5,
s6,
s7,
s8,
s9,
s10,
s11
)
) { arrayResult ->
service(
arrayResult[0] as String,
arrayResult[1] as String,
arrayResult[2] as String?,
arrayResult[3] as String,
arrayResult[4] as String,
arrayResult[5] as String,
arrayResult[6] as String,
arrayResult[7] as String,
arrayResult[8] as String,
arrayResult[9] as String?,
arrayResult[10] as String
)
}
//Then
result
.test()
.awaitDone(50, TimeUnit.SECONDS)
.assertSubscribed()
.assertValues("Values:v1;2;none;4;v5;v6;v7;v8;v9;none;v11")
As you can see, problems may occur if I do for example:
arrayResult[0] as String,
arrayResult[1] as Int,
arrayResult[2] as String?,
arrayResult[3] as Int,
arrayResult[4] as String,
arrayResult[5] as String,
arrayResult[6] as String,
arrayResult[7] as String,
arrayResult[8] as String,
arrayResult[9] as String?,
arrayResult[10] as String
Fails because:
1) None of the Single.zip() functions can take a nullable value as argument.
2) You can change in the array the order of the values and it can fail because of type-check casting
A function with eleven parameters is a good example for unclean code. Instead you should consider to build a model to serve your needs. Like this you can provide meaningful names for each argument as well.
data class MyObject(...)
class MyMutableObject {
private lateinit var param0: String
private var param1: Int
...
fun setParam0(value: String) {
param0 = value
}
fun setParam1(value: Int) {
param1 = value
}
...
fun toMyObject() = MyObject(
param0,
param1,
...
)
}
Having this model you could just use the zipWith() operator on each of your sources.
Single.just(MyMutableObject())
.zipWith(source0, MyMutableObject::setParam0)
.zipWith(source1, MyMutableObject::setParam1)
...
.map(MyMutableObject::toMyObject)
If you consider to abstract the nullability as a Maybe, you could simply define an extension function receiving a Maybe with data or without data and map it appropriately.
inline fun <T, U, R> Single<T>.zipWith(
other: MaybeSource<U>,
crossinline zipper: (T, U) -> R
) = other.zipWith(toMaybe()) { t, u -> zipper(t, u) }
.switchIfEmpty(this)
I have accomplished that goal using:
Kotlin Extension Functions
Curried functions (Kotlin allows that)
Partial Application (Kotlin allows that too)
Functor and Applicative Functors concepts (Single and Observable classes are Applicative functors)
Mix it all together:
First, the zipOver function, for not Nullable values:
/**
* Returns a Single that is the result of applying the function inside the context (a Single in this case).
* This function is curried and will be used as an Applicative Functor, so each argument will be given
* one by one
* #param <B> the result value type
* #param applicativeValue
* a Single that contains the input value of the function
* #return the Single returned when the function is applied to the applicative value.
* Each application will be executed on <b>a new thread</b> if and only if the Single is subscribed on a specific scheduler
*/
infix fun <A, B> Single<(A) -> (B)>.zipOver(applicativeValue: Single<A>): Single<B> =
Single.zip(this, applicativeValue, BiFunction { f, a -> f(a) })
Then, zipOverNullable for Nullable values:
/**
* Returns a Single that is the result of applying the function inside the context (a Single in this case).
* This function is curried and will be used as an Applicative Functor, so each argument will be given
* one by one
* #param <B> the result value type
* #param applicativeValue
* a Single that contains the input value of the function and it can be null
* #return the Single returned when the function is applied to the applicative value even when
* it is null.
* Each application will be executed on <b>a new thread</b> if and only if the Single is subscribed on a specific scheduler
*/
infix fun <A, B> Single<(A?) -> (B)>.zipOverNullable(applicativeValue: Single<A>?): Single<B> =
when {
applicativeValue != null -> Single.zip(this, applicativeValue, BiFunction { f, a -> f(a) })
else -> this.map { it(null) }
}
I used org.funktionale.currying for the curried() function
By combining those two you could write:
//Given
val bothSubscribed = CountDownLatch(0) // Change this value to 2 to run the test slowly
val subscribeThreadsStillRunning = CountDownLatch(0) // Change this value to 1 to run the test slowly
val service: (String, String, String?, String, String, String, String, String, String, String?, String) -> Single<String> = {
s1: String,
s2: Int,
s3: String?,
s4: Int,
s5: String,
s6: String,
s7: String,
s8: String,
s9: String,
s10: String?,
s11: String ->
val result =
listOf(s1, "$s2", s3 ?: "none", "$s4", s5, s6, s7, s8, s9, s10 ?: "none", s11).joinToString(separator = ";")
Single.just("Values:$result")
}
val createSingle = { value: String ->
Observable
.create<String> { emitter ->
println("Parallel subscribe $value on ${Thread.currentThread().name}")
bothSubscribed.countDown()
subscribeThreadsStillRunning.await(20, TimeUnit.SECONDS)
emitter.onNext(value)
emitter.onComplete()
}
.singleOrError()
.subscribeOn(io())
}
val s1: Single<String> = createSingle("v1")
val s2: Single<Int> = Single.just(2)
// Here, we move the Nullable value outside, so the whole Single<String> is Nullable, and not the value inside the Single`enter code here`
val s3: Single<String>? = null
val s4: Single<String> = Single.just(4)
val s5: Single<String> = createSingle("v5")
val s6: Single<String> = createSingle("v6")
val s7: Single<String> = createSingle("v7")
val s8: Single<String> = createSingle("v8")
val s9: Single<String> = createSingle("v9")
val s10: Single<String>? = null
val s11 = createSingle("v11")
//When
// Here I curry the function, so I can apply one by one the the arguments via zipOver() and preserve the types
val singleFunction: Single<(String) -> (String) -> (String?) -> (String) -> (String) -> (String) -> (String) -> (String) -> (String) -> (String?) -> (String) -> Single<String>> =
Single.just(service.curried()).subscribeOn(io())
val result = singleFunction
.zipOver(s1)
.zipOver(s2)
.zipOverNullable(s3)
.zipOver(s4)
.zipOver(s5)
.zipOver(s6)
.zipOver(s7)
.zipOver(s8)
.zipOver(s9)
.zipOverNullable(s10)
.zipOver(s11)
.flatMap { it }
//Then
result
.test()
.awaitDone(50, TimeUnit.SECONDS)
.assertSubscribed()
.assertValues("Values:v1;2;none;4;v5;v6;v7;v8;v9;none;v11")
Then it prints something like:
Parallel subscribe v11 on RxCachedThreadScheduler-10
Parallel subscribe v8 on RxCachedThreadScheduler-8
Parallel subscribe 4 on RxCachedThreadScheduler-4
Parallel subscribe v5 on RxCachedThreadScheduler-5
Parallel subscribe v9 on RxCachedThreadScheduler-9
Parallel subscribe 2 on RxCachedThreadScheduler-3
Parallel subscribe v6 on RxCachedThreadScheduler-6
Parallel subscribe v1 on RxCachedThreadScheduler-2
Parallel subscribe v7 on RxCachedThreadScheduler-7
Now, if I do:
val result = singleFunction
.zipOver(s1)
.zipOver(s1)
.zipOverNullable(s3)
.zipOver(s1)
.zipOver(s5)
.zipOver(s6)
.zipOver(s7)
.zipOver(s8)
.zipOver(s9)
.zipOverNullable(s10)
.zipOver(s11)
.flatMap { it }
It will break at compilation time

How to create an 'andThen' as an infix operator for composability on a generic class in Kotlin?

Problem Statement: I'm trying to recreate Scala/Finagle's andThen method chaining/composition across two types: Filters and Services.
The goal is to be able to do something like this:
val f1 = Filter1()
val f2 = Filter2()
val s3 = Service3()
val pipeline = f1 andThen f2 andThen s3
val result = pipeline(4) //execute pipeline with integer value of 4
Filters should be combinable with other filters and also a service to "end a chain". Services should also be combinable with other services. Both seem to lead to Unresolved reference andThen
Existing non-working solution:
typealias Transformer<A,B> = (A) -> B
abstract class Service<A,B>: Transformer<A,B> {
//DOESN'T WORK
infix fun <A,B,C> Service<A,B>.andThen(f: Service<B,C>): Service<A,C> {
val left = this
return object : Service<A, C>() {
override fun invoke(p1: A): C {
return f(left.invoke(p1))
}
}
}
}
typealias TwoWayTransformer<A,B,C,D> = (A, Service<C,D>) -> B
abstract class Filter<A,B,C,D>: TwoWayTransformer<A,B,C,D> {
//DOESN'T WORK
infix fun <A,B,E,F> Filter<A,B,C,D>.andThen(next: Filter<C,D,E,F>): Filter<A,B,E,F> {
val left = this
return object: Filter<A,B,E,F>() {
override fun invoke(a: A, service: Service<E,F>): B {
val s = object: Service<C,D>() {
override fun invoke(c: C): D { return next.invoke(c,service) }
}
return left.invoke(a,s)
}
}
}
//DOESN'T WORK
infix fun <A,B,C,D> Filter<A,B,C,D>.andThen(next: Service<C,D>): Service<A,B> {
val left = this
return object: Service<A,B>() {
override fun invoke(a: A): B {
return left.invoke(a, next)
}
}
}
}
Sidebar:
Filter<A,B,C,D> can stitch with Filter<C,D,E,F> which can stitch with Service<E,F> - the last two types of the left must match with the first two of the right when doing left andThen right.
A Filter<A,B,C,D> is simply a function of type: (A, Service<C,D>) -> E which simplifies further to (A, C->D) -> E
Link to working fiddle with example services/filters: https://pl.kotl.in/yIx80SzDF
The signatures you need are
infix fun <C> andThen(f: Service<B,C>): Service<A,C>
infix fun <E,F> andThen(next: Filter<C,D,E,F>): Filter<A,B,E,F>
infix fun andThen(next: Service<C,D>): Service<A,B>
Never add any type variables to functions that are already declared in the definition of the class. Never add an extra receiver for the class itself.

Kotlin Coroutines zip three Flows

There is zip function to zip two Flows. Is there something to zip three (or more) Flows together?
If not, can you help me to implement extension function for it? Something like:
flow.zip(flow2, flow3) { a, b, c ->
}
I am new to Flows but this seems to be working for me.
// This will hold all 3 values
data class Foo(val i: Int, val j: Int, val k: Int)
val flow1 = (1..10).asFlow()
val flow2 = (11..20).asFlow()
val flow3 = (21..30).asFlow()
val combinedFlow = flow1.zip(flow2) {i, j ->
Pair(i, j)
}.zip(flow3) {pair, k ->
Foo(pair.first, pair.second, k)
}
Then you would collect them and get the values out like this:
viewModelScope.launch {
repo.combinedFlow.collect {foo ->
System.out.println(foo.i)
System.out.println(foo.j)
System.out.println(foo.k)
}
}
You can check the zip operator implementation and try to copy/emulate how it works adapting it to your needs.
Test it and make all the changes you need
fun <T1, T2, T3, R> Flow<T1>.zip(flow2: Flow<T2>, flow3: Flow<T3>, transform: suspend (T1, T2, T3) -> R): Flow<R> = channelFlow {
val first: ReceiveChannel<T1> = produce {
this#zip.collect {
channel.send(it)
}
}
val second: ReceiveChannel<T2> = produce {
flow2.collect {
channel.send(it)
}
}
val third: ReceiveChannel<T3> = produce {
flow3.collect {
channel.send(it)
}
}
(second as SendChannel<*>).invokeOnClose {
if (!first.isClosedForReceive) first.cancel(MyFlowException())
if (!third.isClosedForReceive) third.cancel(MyFlowException())
}
(third as SendChannel<*>).invokeOnClose {
if (!first.isClosedForReceive) first.cancel(MyFlowException())
if (!second.isClosedForReceive) second.cancel(MyFlowException())
}
val otherIterator = second.iterator()
val anotherIterator = third.iterator()
try {
first.consumeEach { value ->
if (!otherIterator.hasNext() || !anotherIterator.hasNext()) {
return#consumeEach
}
send(transform(value, otherIterator.next(), anotherIterator.next()))
}
} catch (e: MyFlowException) {
// complete
} finally {
if (!second.isClosedForReceive) second.cancel(MyFlowException())
if (!third.isClosedForReceive) third.cancel(MyFlowException())
}
}
class MyFlowException: CancellationException()
Usage:
flow1.zip(flow2, flow3) { a, b, c ->
//Do your work
}...
I have not tested this, but you can give it a try. There's a lot of underlying code for zip, so to exploit that I'm zipping the first two Flows into a Flow of Pairs, and then zip the Flow of Pairs to the third Flow. But the lambda passed to this function gets the first two already separated so it doesn't have to know about the intermediate Pair step.
fun <T1, T2, T3, R> zip(
first: Flow<T1>,
second: Flow<T2>,
third: Flow<T3>,
transform: suspend (T1, T2, T3) -> R
): Flow<R> =
first.zip(second) { a, b -> a to b }
.zip(third) { (a, b), c ->
transform(a, b, c)
}
Usage like this:
zip(flow1, flow2, flow3) { a, b, c ->
Triple(a, b, c)
}
And here's an untested version for an arbitrary number of flows, but they have to be of the same type:
fun <T, R> zip(
vararg flows: Flow<T>,
transform: suspend (List<T>) -> R
): Flow<R> = when(flows.size) {
0 -> error("No flows")
1 -> flows[0].map{ transform(listOf(it)) }
2 -> flows[0].zip(flows[1]) { a, b -> transform(listOf(a, b)) }
else -> {
var accFlow: Flow<List<T>> = flows[0].zip(flows[1]) { a, b -> listOf(a, b) }
for (i in 2 until flows.size) {
accFlow = accFlow.zip(flows[i]) { list, it ->
list + it
}
}
accFlow.map(transform)
}
}
Flow has combine function
fun <T1, T2, R> Flow<T1>.combine(
flow: Flow<T2>,
transform: suspend (a: T1, b: T2) -> R
): Flow<R>
It returns a Flow whose values are generated with transform function by combining the most recently emitted values by each flow.
Source
Example from srouces:
val flow = flowOf(1, 2).onEach { delay(10) }
val flow2 = flowOf("a", "b", "c").onEach { delay(15) }
combine(flow, flow2) { i, s -> i.toString() + s }.collect {
println(it) // Will print "1a 2a 2b 2c"
}

How to 'zip' two or more coroutine channels?

So in RxJava, we could simply do:
Observable.zip(someObservable, anotherObservable, BiFunction { a, b -> //do something }.subscribe { // do something }
How do we do the same thing with Kotlin Coroutine Channels?
not ideal solution but it seems to work
#ExperimentalCoroutinesApi
private fun <T, R> CoroutineScope.zipChannels(
channel1: ReceiveChannel<T>,
channel2: ReceiveChannel<T>,
zip: (T, T) -> R
): ReceiveChannel<R> = produce {
val iterator1 = channel1.iterator()
val iterator2 = channel2.iterator()
while (iterator1.hasNext() && iterator2.hasNext()) {
val value1 = iterator1.next()
val value2 = iterator2.next()
send(zip(value1, value2))
}
channel1.cancel()
channel2.cancel()
close()
}
Update
Also, there is a deprecated operator zip

How to do parallel flatMap in Kotlin?

I need to do parallel flat map.
Let's say I have this code:
val coll: List<Set<Int>> = ...
coll.flatMap{set -> setOf(set, set + 1)}
I need something like this:
coll.pFlatMap{set -> setOf(set, set + 1)} // parallel execution
Kotlin doesn’t provide any threading out of the box.
But you can use kotlinx.coroutines to do something like this:
val coll: List<Set<Int>> = ...
val result = coll
.map {set ->
// Run each task in own coroutine,
// you can limit concurrency using custom coroutine dispatcher
async { doSomethingWithSet(set) }
}
.flatMap { deferred ->
// Await results and use flatMap
deferred.await() // You can handle errors here
}
Alternatively, you can do it without coroutines:
fun <T, R> Collection<T>.pFlatMap(transform: (T) -> Collection<R>): List<R> =
parallelStream().flatMap { transform(it).stream() }.toList()
This solution requires Kotlin on JDK 8 or higher.
You can also make it more general (analogous to Kotlin's flatMap):
fun <T, R> Iterable<T>.pFlatMap(transform: (T) -> Iterable<R>): List<R> =
toList()
.parallelStream()
.flatMap { transform(it).toList().stream() }
.toList()
You need to add Coroutine scope first (runBlocking), and apply deferred execution to your function (async):
val coll: List<Set<Int>> = listOf()
val x = runBlocking {
coll.map{ set ->
async {
setOf(set, set + 1)
}
}.awaitAll().flatten()
}
x ....