kotlin-result icon indicating copy to clipboard operation
kotlin-result copied to clipboard

feat(parZip): add `parZip` functions for combining results of 2 to 5 computations in parallel

Open hoc081098 opened this issue 7 months ago • 3 comments

This pull request introduces a new parZip utility to the kotlin-result-coroutines library, which allows running multiple computations in parallel and combining their results. It also includes comprehensive test cases to validate the functionality of parZip for various scenarios.

  • Inspired by arrow-fx-coroutines parZip.

    Regarding the name parZip, I chose it by following the naming convention of the Arrow.kt library. If you have a better suggestion, I will update this PR accordingly 🙏.

  • Added parZip functions for combining results of 2 to 5 computations in parallel. If any computation fails, the others are cancelled, and the error is returned.

  • Added ParZipTest class with test cases to verify the behavior of parZip for 2 to 5 computations.

Example


data class Movie(val id: Int, val title: String)

suspend fun getFavoriteMovies(): Result<List<Movie>, String> {
    delay(100) // simulate network delay
    return Ok(
        listOf(
            Movie(1, "Inception"),
            Movie(2, "The Matrix")
        )
    )
}

suspend fun getPopularMovies(): Result<List<Movie>, String> {
    delay(100) // simulate network delay
    return Ok(
        listOf(
            Movie(3, "Avengers: Endgame"),
            Movie(4, "Titanic")
        )
    )
}

suspend fun getTopRatedMovies(): Result<List<Movie>, String> {
    delay(100) // simulate network delay
    return Ok(
        listOf(
            Movie(5, "The Shawshank Redemption"),
            Movie(6, "The Godfather")
        )
    )
}

// --------------------------------------------------------

data class HomePageMovies(
    val favoriteMovies: List<Movie>,
    val popularMovies: List<Movie>,
    val topRatedMovies: List<Movie>
)

suspend fun getHomePageMoviesParZip(): Result<HomePageMovies, String> =
    withContext(Dispatchers.IO) {
        parZip(
            { getFavoriteMovies() },
            { getPopularMovies() },
            { getTopRatedMovies() },
        ) { favoriteMovies, popularMovies, topRatedMovies ->
            HomePageMovies(
                favoriteMovies = favoriteMovies,
                popularMovies = popularMovies,
                topRatedMovies = topRatedMovies
            )
        }
    }
// Compare getHomePageMoviesParZip with getHomePageMoviesSequentially:
// The execution time of getHomePageMoviesParZip is significantly less than that of getHomePageMoviesSequentially.
// -> it will improve the app performance and user experience.

suspend fun getHomePageMoviesSequentially(): Result<HomePageMovies, String> =
    withContext(Dispatchers.IO) {
        coroutineBinding {
            HomePageMovies(
                favoriteMovies = getFavoriteMovies().bind(),
                popularMovies = getPopularMovies().bind(),
                topRatedMovies = getTopRatedMovies().bind(),
            )
        }
    }

hoc081098 avatar Apr 27 '25 14:04 hoc081098

nice, This operator is indeed necessary, but can we add a context parameter ctx: CoroutineContext = EmptyCoroutineContext, to adjust its behavior?

hoangchungk53qx1 avatar Apr 28 '25 02:04 hoangchungk53qx1

Thanks for this - I like this idea.

I am slightly uncomfortable with the implementation, as it seems to share a large amount of code with coroutineBinding itself, notably the creation of a coroutineScope, a custom ParZipException, etc.

I think I would prefer if the parZip implementation leveraged as much of the existing coroutineBinding function as possible, to reduce maintenance overhead and avoid introducing more custom exceptions.

I can imagine it will end up looking like the body (below) is wrapped in a coroutineBinding:

            val values = producers
                .map { producer -> async { producer().getOrThrow(::ParZipException) } }
                .awaitAll()

Can you give this a go and let me know if the existing coroutineBinding doesn't work for you?

michaelbull avatar Apr 28 '25 13:04 michaelbull

Can you give this a go and let me know if the existing coroutineBinding doesn't work for you?

Thanks. I've just updated the PR following your suggestion 👍.

hoc081098 avatar Apr 28 '25 15:04 hoc081098

Merged in b205cf2f06d06936d71edb2c19301b7f414c01ec, thanks!

michaelbull avatar Aug 03 '25 13:08 michaelbull