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"
}
Related
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.
I'd like to make a function that takes a variable number of arguments of different types, and a closure, and call the closure with the same number of arguments, each corresponding to a type in the original argument list:
fun <A, B, ...>mergeWhenValid(
arg1: Either<Problem, A>,
arg2: Either<Problem, B>,
...,
closure: (A, B, ...) -> T
): Either<Problem, T> {
// do stuff and call closure(a, b, ...)
}
How might I accomplish this?
If your mergeWhenValid just returns closure result if all eithers are right and firstProblem.left() otherwise, you should use Either.fx<Problem, T> instead of your function. Example:
Either.fx<Problem, String> { "${eitherInt.bind()} ${eitherDouble.bind()} ${eitherFloat.bind()}" }
If your logic is more complex and you need somehow handle all eithers, you can do it either by creating special merging DSL:
fun <R> mergeWhenValid(block: MergeWhenValidScope.() -> R): R = MergeWhenValidScope().block()
class EitherProblem<out T>(internal val either: Either<Problem, T>)
class MergeWhenValidScope {
private val eithers = mutableListOf<Either<Problem, *>>()
operator fun <T> Either<Problem, T>.component1(): EitherProblem<T> {
eithers += this
return EitherProblem(this)
}
private fun doStuff(): Option<Problem> {
// you can use `eithers` here and choose one of their problems or create a new one
// if you return None, it will assume that all `eithers` are right,
// otherwise, problem will be wrapped in Either and returned
return eithers.asSequence().mapNotNull { it.swap().getOrElse { null } }.firstOption()
}
fun <R> combine(block: CombinerScope.() -> R): Either<Problem, R> =
doStuff().map { it.left() }.getOrElse { CombinerScope.block().right() }
object CombinerScope {
operator fun <T> EitherProblem<T>.invoke() = either.getOrHandle {
error("Unexpected problem $it")
}
}
}
Use case:
mergeWhenValid {
val (int) = eitherInt
val (double) = eitherDouble
val (float) = eitherFloat
combine { "${int()} ${double()} ${float()}" }
}
Or by pipelining functions which add all your eithers to some object:
fun <T> mergeWhenValid() = MergeWhenValidInit<T>()
class MergeWhenValidInit<T> {
operator fun <A> invoke(either: Either<Problem, A>): MergeWhenValid<A, T, T> =
MergeWhenValid(either, listOf(either)) { it }
}
class MergeWhenValid<A, B, C>(
private val either: Either<Problem, A>,
private val eithers: List<Either<Problem, *>>,
private val previous: (B) -> C // is allowed to be called only if all `eithers` are right
) {
private fun doStuff(): Option<Problem> {
// you can use `eithers` here and choose one of their problems or create a new one
// if you return None, it will assume that all `eithers` are right,
// otherwise, problem will be wrapped in Either and returned
return eithers.asSequence().mapNotNull { it.swap().getOrElse { null } }.firstOption()
}
operator fun invoke(block: (A) -> B): Either<Problem, C> =
doStuff().map { it.left() }.getOrElse { requireC(block).right() }
operator fun <D> invoke(either: Either<Problem, D>): MergeWhenValid<D, (A) -> B, C> =
MergeWhenValid(either, eithers + either) { next -> requireC(next) }
private fun requireC(next: (A) -> B): C = previous(next(either.getOrHandle {
error("Unexpected problem $it")
}))
}
Use case:
mergeWhenValid<String>()(eitherInt)(eitherDouble)(eitherFloat)() { float ->
{ double -> { int -> "$int $double $float" } }
}
Note: the last approach reverses the order of arguments and also forces you to write { c -> { b -> { a -> ... } } } instead of { c, b, a -> ... }.
Is there a more idiomatic way to write the following?
foo?.let{
if(!foo.isBlank()) {
bar?.let {
if(!bar.isBlank()) {
println("foo and bar both valid strings")
}
}
}
}
basically this the idea is that both strings should be nonNull and nonEmpty and I was wondering if there is a more Kotlin way than doing if(foo.isNullOrEmpty && !bar.isNullOrEmpty)
Use this
fun <T, R, S> biLet(lhs: T, rhs: R, block: (T, R) -> S): S? = if (lhs != null && rhs != null) block(lhs, rhs) else null
Use as
biLet(foo, bar) { safeFoo, safeBar ->
}
Edit: variant for strings
fun <T: CharSequence?, S> biLet(lhs: T, rhs: T, block: (T, T) -> S): S? =
if (lhs.isNotNullOrBlank() && rhs.isNotNullOrBlank()) block(lhs, rhs) else null
You can use sequenceOf and none:
if (sequenceOf(foo, bar).none { it.isNullOrBlank() }) {
println("foo and bar both valid strings")
}
Declare somewhere an extension function using lambdas like:
inline fun String.ifNotEmpty(bar: String, function: () -> Unit) {
if (this.isNotEmpty() && bar.isNotEmpty()) {
function.invoke()
}
}
And use it as:
val foo = "foo-value"
val bar = "bar-value"
foo.ifNotEmpty(bar) {
println("foo and bar both valid strings")
}
Improving #Francesc answer, I created a nLet version
fun <S> nLet(vararg ts: Any?, block: (Array<out Any?>) -> S): S? =
if (ts.none { when (it) { is String -> it.isNullOrEmpty() else -> it == null } }) block(ts) else null
You can use it like that
nLet (1, 2 , 3, "a", "B", true) { ts ->
ts.forEach { println(it) }
}
This is what I use:
fun <P1, P2, R> nLet(p1: P1?, p2: P2?, block: (P1, P2) -> R?): R? =
p1?.let { p2?.let { block(p1, p2) } }
Usage:
nLet(foo, bar) { f, b -> doStuff(f, b) }
Add more nLet functions with more P's if more arguments are needed.
You can also use this for an arbitary number of arguments:
fun <P, R> nLet(vararg ts: P?, block: (Array<out P?>) -> R): R? =
ts.takeIf { it.none { it == null } }?.let { block(it) }
Usage:
nLet(foo, bar, dog) { (f, b, d) -> doStuff(f, b, d) }
This works, but f, b and d will have nullable types even though they cannot be null.
(There might be a clever way to solve that...)
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
I need to implement the following .combineLatest() extension function on a ReceiveChannel
suspend fun <A, B, R> ReceiveChannel<A>.combineLatest(
otherSource: ReceiveChannel<B>,
context: CoroutineContext = Unconfined,
combineFunction: suspend (A, B) -> R
): ReceiveChannel<R> = produce(context) {
// ?
}
I would like it to function just like RxJava's combineLatest().
How can I do this?
EDIT: So far I have this, but it's not working. The sourceB.consumeEach{ } block is never excecuted.
suspend fun <A, B, R> ReceiveChannel<A>.combineLatest(
otherSource: ReceiveChannel<B>,
context: CoroutineContext = Unconfined,
combineFunction: suspend (A, B) -> R
): ReceiveChannel<R> = produce(context) {
val sourceA: ReceiveChannel<A> = this#combineLatest
val sourceB: ReceiveChannel<B> = otherSource
var latestA: A? = null
var latestB: B? = null
sourceA.consumeEach { a ->
latestA = a
if (latestA != null && latestB != null) {
send(combineFunction(latestA!!, latestB!!))
}
}
sourceB.consumeEach { b ->
latestB = b
if (latestA != null && latestB != null) {
send(combineFunction(latestA!!, latestB!!))
}
}
}
I also want to make sure that when the ReceiveChannel<R> returned by this function is closed (unsubscribed from), I want to make sure that the parent channels are closed properly.
This did the trick! I am still confused why I can nest one .consumeEach{ } inside another .consumeEach { } - it seems unintuitive.
suspend fun <A, B, R> ReceiveChannel<A>.combineLatest(
otherSource: ReceiveChannel<B>,
context: CoroutineContext = Unconfined,
combineFunction: suspend (A, B) -> R
): ReceiveChannel<R> = produce(context) {
val sourceA: ReceiveChannel<A> = this#combineLatest
val sourceB: ReceiveChannel<B> = otherSource
val latestA = AtomicReference<A>()
val latestB = AtomicReference<B>()
var aInitialized = false
var bInitialized = false
sourceA.consumeEach { a ->
latestA.set(a)
aInitialized = true
if (aInitialized && bInitialized) {
send(combineFunction(latestA.get(), latestB.get()))
}
launch(coroutineContext) {
sourceB.consumeEach { b ->
latestB.set(b)
bInitialized = true
if (aInitialized && bInitialized) {
send(combineFunction(latestA.get(), latestB.get()))
}
}
}
}
}
I understand this is an old question but here is a suggestion:
I would recommned using .zip() instead of nesting .consumeEach. Checkout the documentation here.
Possible solution sourceA.zip(sourceB).consumeEach{} which produces an item of type Pair.