Fleshgrinder
06/24/2022, 1:56 PMval result = awaitAll(
async { networkFlow.toSet() },
async { filesystemFlow.toList() },
)
val networkResult = result[0] as Set<NetworkResult>
val filesystemResult = result[1] as List<FilesystemResult>
This works exactly as intended, but the ceremony required to get it to work makes it seem even worse than threading and futures. I was hoping for at least …
val (networkResult, filesystemResult) = awaitAll(
async { networkFlow.toSet() },
async { filesystemFlow.toList() },
)
… but this does not exist. Any other way to make this nicer? 🤔mkrussel
06/24/2022, 2:00 PMval networkDeferred = async { networkFlow.toSet() }
val filesystemResult = filesystem.toList()
val networkResult = networkDeferred.await()
Fleshgrinder
06/24/2022, 2:03 PMyschimke
06/24/2022, 2:25 PMyschimke
06/24/2022, 2:25 PMval (network, filesystem) = listOf(
async { networkFlow() },
async { filesystemFlow() },
).awaitAll()
yschimke
06/24/2022, 2:26 PMFleshgrinder
06/24/2022, 2:27 PMyschimke
06/24/2022, 2:29 PMyschimke
06/24/2022, 2:29 PMval (network, filesystem) = awaitAll(
async { networkFlow() },
async { filesystemFlow() },
)
yschimke
06/24/2022, 2:30 PMyschimke
06/24/2022, 2:30 PMyschimke
06/24/2022, 2:30 PMFleshgrinder
06/24/2022, 3:12 PMstojan
06/24/2022, 6:05 PMparZip
from Arrow
you can check the implementation here..... it's basically a generic version of your code: https://github.com/arrow-kt/arrow/blob/051847de3b1d3186fc9843864aaf642a2f4a854f/ar[…]-coroutines/src/commonMain/kotlin/arrow/fx/coroutines/ParZip.ktFleshgrinder
06/24/2022, 8:09 PMTijl
06/25/2022, 7:56 PMimport kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
suspend fun main() {
val network = flow { emit("foo") }
val filesystem = flow { emit(42) }
val (networkResult, filesystemResult) = network.zip(filesystem) { network, filesystem -> network to filesystem }.first()
println("$networkResult and $filesystemResult")
}
https://pl.kotl.in/bcWP5F2UnTijl
06/25/2022, 7:59 PMzipToPair
extension method, if it does not exist somewhere alreadyFleshgrinder
06/26/2022, 6:44 AMtoList
or toSet
on one of both first.uli
06/26/2022, 12:17 PMSo it seems this is the way to go. I checked the individual await as suggested by
@mkrussel
but it would not properly work without copying all the stuff from the awaitAll impl.
What would go wrong with @mkrussel’s exact solution?uli
06/26/2022, 12:20 PMval networkResultDeferred = async { networkFlow.toSet() }
val filesystemResultDeferred = async { filesystemFlow.toList() }
val networkResult = networkResultDeferred.await()
val filesystemResult = filesystemResultDeferred.await()
Tijl
06/26/2022, 12:24 PMtoList
nor toSet
in my example, but you can just do this inside the flow flow { emit(whateverFlow.toList()) }
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
suspend fun main() {
val networkF = flowOf("foo", "bar")
val network = flow { emit(networkF.toList()) }
val fileF = flowOf(4, 2)
val filesystem = flow { emit(fileF.toSet()) }
val (networkResult, filesystemResult) = network.zip(filesystem) { network, filesystem -> network to filesystem }.first()
println("$networkResult and $filesystemResult")
}
https://pl.kotl.in/6j74wOg06
the point is trying to combine two async results is hard, combining two flows is easy. You can shortcut this even more but I think the above example best demonstrates why this works.Fleshgrinder
06/26/2022, 12:30 PMtoList
or toSet
, but my question did. 😝 Since it’s more code than the original that is also less clear I prefer the original solution, especially if hidden behind something like the parZip
of Arrow. However, I’d be curious which one performs better. 🤔Tijl
06/26/2022, 12:55 PMawaitAll
is exempted?), but it has to be as compact as possible.
it doesn’t really even matter which approach you take, it’s pretty much the same solution (work is mostly in reading a file or doing a network call in parallel, not in combining the result). Both can be made into val (one, two) = collectBothConcurrent(flow1, flow2)
or something similar.
Personally I avoid unneeded as
hard casting like the plague (unless you don’t mind warnings you’ll need a suppress I think), but each to their own. I guess you like low line/symbol count more.Fleshgrinder
06/26/2022, 1:17 PMawaitAll
overloads that accept 2 to 26 elements, preserve their types, and allow destructuring. It would make the Arrow impl obsolete and allow everyone to use it without worrying about weird side effects or adding unnecessary overhead.Fleshgrinder
06/26/2022, 1:22 PMTijl
06/26/2022, 2:58 PMawaitAll
(or one day static analysis will be good enough to allow the current construct without force casting)uli
06/26/2022, 3:24 PMcoroutineScope {}
around the code I posted, to get common, local error handling and be done with it.
Unless your see any issue with it. Just because other code with other requirements is complex does not mean the simple code is wrong.Fleshgrinder
06/26/2022, 5:12 PMawaitAll
is as complex inside as it is. If all of the ceremony is for nothing than the two awaits are clearly the nicest solution.uli
06/26/2022, 6:43 PMFleshgrinder
06/26/2022, 6:56 PMawaitAll
we get an exception immediately, because it tracks them all, and aborts all upon the first exception.
This would mean in my case that the network call should be inner and the filesystem call should be awaited. Simply because the network has more unhappy paths.
It's the only gotcha I see and for me this is still clearly superior over everything else, since it ticks all boxes:
• Short
• Fast
• Lightweight
• Typesafe
• No need for additional objects that aren't strictly necessary
• No need for additional types just to capture types
• No need for destructuring
• ...Tijl
06/26/2022, 10:30 PMFleshgrinder
06/27/2022, 6:14 AM