|
| 1 | +package com.github.michaelbull.result.coroutines |
| 2 | + |
| 3 | +import com.github.michaelbull.result.Err |
| 4 | +import com.github.michaelbull.result.Result |
| 5 | +import kotlinx.coroutines.CoroutineScope |
| 6 | +import kotlinx.coroutines.async |
| 7 | +import kotlinx.coroutines.awaitAll |
| 8 | +import kotlin.contracts.InvocationKind |
| 9 | +import kotlin.contracts.contract |
| 10 | + |
| 11 | +private typealias Producer<T, E> = suspend CoroutineScope.() -> Result<T, E> |
| 12 | + |
| 13 | +private suspend inline fun <T, E, V> parZipInternal( |
| 14 | + vararg producers: Producer<T, E>, |
| 15 | + crossinline transform: suspend CoroutineScope.(values: List<T>) -> V, |
| 16 | +): Result<V, E> { |
| 17 | + contract { |
| 18 | + callsInPlace(transform, InvocationKind.AT_MOST_ONCE) |
| 19 | + } |
| 20 | + |
| 21 | + return coroutineBinding { |
| 22 | + val values = producers.map { producer -> |
| 23 | + async { |
| 24 | + producer().bind() |
| 25 | + } |
| 26 | + } |
| 27 | + |
| 28 | + transform(values.awaitAll()) |
| 29 | + } |
| 30 | +} |
| 31 | + |
| 32 | +/** |
| 33 | + * Applies the given [transform] function to two [Results][Result] _in parallel_, returning early |
| 34 | + * with the first [Err] if a transformation fails. |
| 35 | + * |
| 36 | + * - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map2 |
| 37 | + */ |
| 38 | +public suspend fun <T1, T2, E, V> parZip( |
| 39 | + producer1: Producer<T1, E>, |
| 40 | + producer2: Producer<T2, E>, |
| 41 | + transform: suspend CoroutineScope.(T1, T2) -> V, |
| 42 | +): Result<V, E> { |
| 43 | + contract { |
| 44 | + callsInPlace(producer1, InvocationKind.AT_MOST_ONCE) |
| 45 | + callsInPlace(producer2, InvocationKind.AT_MOST_ONCE) |
| 46 | + callsInPlace(transform, InvocationKind.AT_MOST_ONCE) |
| 47 | + } |
| 48 | + |
| 49 | + return parZipInternal(producer1, producer2) { values -> |
| 50 | + @Suppress("UNCHECKED_CAST") |
| 51 | + transform( |
| 52 | + values[0] as T1, |
| 53 | + values[1] as T2, |
| 54 | + ) |
| 55 | + } |
| 56 | +} |
| 57 | + |
| 58 | +/** |
| 59 | + * Applies the given [transform] function to three [Results][Result] _in parallel_, returning early |
| 60 | + * with the first [Err] if a transformation fails. |
| 61 | + * |
| 62 | + * - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map3 |
| 63 | + */ |
| 64 | +public suspend fun <T1, T2, T3, E, V> parZip( |
| 65 | + producer1: Producer<T1, E>, |
| 66 | + producer2: Producer<T2, E>, |
| 67 | + producer3: Producer<T3, E>, |
| 68 | + transform: suspend CoroutineScope.(T1, T2, T3) -> V, |
| 69 | +): Result<V, E> { |
| 70 | + contract { |
| 71 | + callsInPlace(producer1, InvocationKind.AT_MOST_ONCE) |
| 72 | + callsInPlace(producer2, InvocationKind.AT_MOST_ONCE) |
| 73 | + callsInPlace(producer3, InvocationKind.AT_MOST_ONCE) |
| 74 | + callsInPlace(transform, InvocationKind.AT_MOST_ONCE) |
| 75 | + } |
| 76 | + |
| 77 | + return parZipInternal(producer1, producer2, producer3) { values -> |
| 78 | + @Suppress("UNCHECKED_CAST") |
| 79 | + transform( |
| 80 | + values[0] as T1, |
| 81 | + values[1] as T2, |
| 82 | + values[2] as T3, |
| 83 | + ) |
| 84 | + } |
| 85 | +} |
| 86 | + |
| 87 | +/** |
| 88 | + * Applies the given [transform] function to four [Results][Result] _in parallel_, returning early |
| 89 | + * with the first [Err] if a transformation fails. |
| 90 | + * |
| 91 | + * - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map4 |
| 92 | + */ |
| 93 | +public suspend fun <T1, T2, T3, T4, E, V> parZip( |
| 94 | + producer1: Producer<T1, E>, |
| 95 | + producer2: Producer<T2, E>, |
| 96 | + producer3: Producer<T3, E>, |
| 97 | + producer4: Producer<T4, E>, |
| 98 | + transform: suspend CoroutineScope.(T1, T2, T3, T4) -> V, |
| 99 | +): Result<V, E> { |
| 100 | + contract { |
| 101 | + callsInPlace(producer1, InvocationKind.AT_MOST_ONCE) |
| 102 | + callsInPlace(producer2, InvocationKind.AT_MOST_ONCE) |
| 103 | + callsInPlace(producer3, InvocationKind.AT_MOST_ONCE) |
| 104 | + callsInPlace(producer4, InvocationKind.AT_MOST_ONCE) |
| 105 | + callsInPlace(transform, InvocationKind.AT_MOST_ONCE) |
| 106 | + } |
| 107 | + |
| 108 | + return parZipInternal(producer1, producer2, producer3, producer4) { values -> |
| 109 | + @Suppress("UNCHECKED_CAST") |
| 110 | + transform( |
| 111 | + values[0] as T1, |
| 112 | + values[1] as T2, |
| 113 | + values[2] as T3, |
| 114 | + values[3] as T4, |
| 115 | + ) |
| 116 | + } |
| 117 | +} |
| 118 | + |
| 119 | +/** |
| 120 | + * Applies the given [transform] function to five [Results][Result] _in parallel_, returning early |
| 121 | + * with the first [Err] if a transformation fails. |
| 122 | + * |
| 123 | + * - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map5 |
| 124 | + */ |
| 125 | +public suspend fun <T1, T2, T3, T4, T5, E, V> parZip( |
| 126 | + producer1: Producer<T1, E>, |
| 127 | + producer2: Producer<T2, E>, |
| 128 | + producer3: Producer<T3, E>, |
| 129 | + producer4: Producer<T4, E>, |
| 130 | + producer5: Producer<T5, E>, |
| 131 | + transform: suspend CoroutineScope.(T1, T2, T3, T4, T5) -> V, |
| 132 | +): Result<V, E> { |
| 133 | + contract { |
| 134 | + callsInPlace(producer1, InvocationKind.AT_MOST_ONCE) |
| 135 | + callsInPlace(producer2, InvocationKind.AT_MOST_ONCE) |
| 136 | + callsInPlace(producer3, InvocationKind.AT_MOST_ONCE) |
| 137 | + callsInPlace(producer4, InvocationKind.AT_MOST_ONCE) |
| 138 | + callsInPlace(producer5, InvocationKind.AT_MOST_ONCE) |
| 139 | + callsInPlace(transform, InvocationKind.AT_MOST_ONCE) |
| 140 | + } |
| 141 | + |
| 142 | + return parZipInternal(producer1, producer2, producer3, producer4, producer5) { values -> |
| 143 | + @Suppress("UNCHECKED_CAST") |
| 144 | + transform( |
| 145 | + values[0] as T1, |
| 146 | + values[1] as T2, |
| 147 | + values[2] as T3, |
| 148 | + values[3] as T4, |
| 149 | + values[4] as T5, |
| 150 | + ) |
| 151 | + } |
| 152 | +} |
0 commit comments