diff --git a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Zip.kt b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Zip.kt index 567cd45..4f5d7fc 100644 --- a/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Zip.kt +++ b/kotlin-result/src/commonMain/kotlin/com/github/michaelbull/result/Zip.kt @@ -12,18 +12,18 @@ private typealias Producer = () -> Result * - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map2 */ public inline fun zip( - result1: Producer, - result2: Producer, + producer1: Producer, + producer2: Producer, transform: (T1, T2) -> V ): Result { contract { - callsInPlace(result1, InvocationKind.EXACTLY_ONCE) - callsInPlace(result2, InvocationKind.AT_MOST_ONCE) + callsInPlace(producer1, InvocationKind.EXACTLY_ONCE) + callsInPlace(producer2, InvocationKind.AT_MOST_ONCE) callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } - return result1().flatMap { v1 -> - result2().map { v2 -> + return producer1().flatMap { v1 -> + producer2().map { v2 -> transform(v1, v2) } } @@ -36,21 +36,21 @@ public inline fun zip( * - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map3 */ public inline fun zip( - result1: Producer, - result2: Producer, - result3: Producer, + producer1: Producer, + producer2: Producer, + producer3: Producer, transform: (T1, T2, T3) -> V ): Result { contract { - callsInPlace(result1, InvocationKind.EXACTLY_ONCE) - callsInPlace(result2, InvocationKind.AT_MOST_ONCE) - callsInPlace(result3, InvocationKind.AT_MOST_ONCE) + callsInPlace(producer1, InvocationKind.EXACTLY_ONCE) + callsInPlace(producer2, InvocationKind.AT_MOST_ONCE) + callsInPlace(producer3, InvocationKind.AT_MOST_ONCE) callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } - return result1().flatMap { v1 -> - result2().flatMap { v2 -> - result3().map { v3 -> + return producer1().flatMap { v1 -> + producer2().flatMap { v2 -> + producer3().map { v3 -> transform(v1, v2, v3) } } @@ -64,24 +64,24 @@ public inline fun zip( * - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map4 */ public inline fun zip( - result1: Producer, - result2: Producer, - result3: Producer, - result4: Producer, + producer1: Producer, + producer2: Producer, + producer3: Producer, + producer4: Producer, transform: (T1, T2, T3, T4) -> V ): Result { contract { - callsInPlace(result1, InvocationKind.EXACTLY_ONCE) - callsInPlace(result2, InvocationKind.AT_MOST_ONCE) - callsInPlace(result3, InvocationKind.AT_MOST_ONCE) - callsInPlace(result4, InvocationKind.AT_MOST_ONCE) + callsInPlace(producer1, InvocationKind.EXACTLY_ONCE) + callsInPlace(producer2, InvocationKind.AT_MOST_ONCE) + callsInPlace(producer3, InvocationKind.AT_MOST_ONCE) + callsInPlace(producer4, InvocationKind.AT_MOST_ONCE) callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } - return result1().flatMap { v1 -> - result2().flatMap { v2 -> - result3().flatMap { v3 -> - result4().map { v4 -> + return producer1().flatMap { v1 -> + producer2().flatMap { v2 -> + producer3().flatMap { v3 -> + producer4().map { v4 -> transform(v1, v2, v3, v4) } } @@ -96,27 +96,27 @@ public inline fun zip( * - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map5 */ public inline fun zip( - result1: Producer, - result2: Producer, - result3: Producer, - result4: Producer, - result5: Producer, + producer1: Producer, + producer2: Producer, + producer3: Producer, + producer4: Producer, + producer5: Producer, transform: (T1, T2, T3, T4, T5) -> V ): Result { contract { - callsInPlace(result1, InvocationKind.EXACTLY_ONCE) - callsInPlace(result2, InvocationKind.AT_MOST_ONCE) - callsInPlace(result3, InvocationKind.AT_MOST_ONCE) - callsInPlace(result4, InvocationKind.AT_MOST_ONCE) - callsInPlace(result5, InvocationKind.AT_MOST_ONCE) + callsInPlace(producer1, InvocationKind.EXACTLY_ONCE) + callsInPlace(producer2, InvocationKind.AT_MOST_ONCE) + callsInPlace(producer3, InvocationKind.AT_MOST_ONCE) + callsInPlace(producer4, InvocationKind.AT_MOST_ONCE) + callsInPlace(producer5, InvocationKind.AT_MOST_ONCE) callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } - return result1().flatMap { v1 -> - result2().flatMap { v2 -> - result3().flatMap { v3 -> - result4().flatMap { v4 -> - result5().map { v5 -> + return producer1().flatMap { v1 -> + producer2().flatMap { v2 -> + producer3().flatMap { v3 -> + producer4().flatMap { v4 -> + producer5().map { v5 -> transform(v1, v2, v3, v4, v5) } } @@ -124,3 +124,191 @@ public inline fun zip( } } } + +/** + * Apply a [transformation][transform] to two [Results][Result], if both [Results][Result] are [Ok]. + * If not, the all arguments which are [Err] will be collected. + */ +public inline fun zipOrAccumulate( + producer1: () -> Result, + producer2: () -> Result, + transform: (T1, T2) -> V, +): Result> { + contract { + callsInPlace(producer1, InvocationKind.EXACTLY_ONCE) + callsInPlace(producer2, InvocationKind.EXACTLY_ONCE) + callsInPlace(transform, InvocationKind.AT_MOST_ONCE) + } + + val result1 = producer1() + val result2 = producer2() + + return if ( + result1 is Ok && + result2 is Ok + ) { + val transformed = transform( + result1.value, + result2.value, + ) + + Ok(transformed) + } else { + val errors = listOf( + result1, + result2 + ).mapNotNull { it.getError() } + + Err(errors) + } +} + +/** + * Apply a [transformation][transform] to three [Results][Result], if all [Results][Result] are [Ok]. + * If not, the all arguments which are [Err] will be collected. + */ +public inline fun zipOrAccumulate( + producer1: () -> Result, + producer2: () -> Result, + producer3: () -> Result, + transform: (T1, T2, T3) -> V, +): Result> { + contract { + callsInPlace(producer1, InvocationKind.EXACTLY_ONCE) + callsInPlace(producer2, InvocationKind.EXACTLY_ONCE) + callsInPlace(producer3, InvocationKind.EXACTLY_ONCE) + callsInPlace(transform, InvocationKind.AT_MOST_ONCE) + } + + val result1 = producer1() + val result2 = producer2() + val result3 = producer3() + + return if ( + result1 is Ok && + result2 is Ok && + result3 is Ok + ) { + val transformed = transform( + result1.value, + result2.value, + result3.value + ) + + Ok(transformed) + } else { + val errors = listOf( + result1, + result2, + result3 + ).mapNotNull { it.getError() } + + Err(errors) + } +} + +/** + * Apply a [transformation][transform] to four [Results][Result], if all [Results][Result] are [Ok]. + * If not, the all arguments which are [Err] will be collected. + */ +public inline fun zipOrAccumulate( + producer1: () -> Result, + producer2: () -> Result, + producer3: () -> Result, + producer4: () -> Result, + transform: (T1, T2, T3, T4) -> V, +): Result> { + contract { + callsInPlace(producer1, InvocationKind.EXACTLY_ONCE) + callsInPlace(producer2, InvocationKind.EXACTLY_ONCE) + callsInPlace(producer3, InvocationKind.EXACTLY_ONCE) + callsInPlace(producer4, InvocationKind.EXACTLY_ONCE) + callsInPlace(transform, InvocationKind.AT_MOST_ONCE) + } + + val result1 = producer1() + val result2 = producer2() + val result3 = producer3() + val result4 = producer4() + + return if ( + result1 is Ok && + result2 is Ok && + result3 is Ok && + result4 is Ok + ) { + val transformed = transform( + result1.value, + result2.value, + result3.value, + result4.value + ) + + Ok(transformed) + } else { + val errors = listOf( + result1, + result2, + result3, + result4 + ).mapNotNull { it.getError() } + + Err(errors) + } +} + +/** + * Apply a [transformation][transform] to five [Results][Result], if all [Results][Result] are [Ok]. + * If not, the all arguments which are [Err] will be collected. + */ +public inline fun zipOrAccumulate( + producer1: () -> Result, + producer2: () -> Result, + producer3: () -> Result, + producer4: () -> Result, + producer5: () -> Result, + transform: (T1, T2, T3, T4, T5) -> V, +): Result> { + contract { + callsInPlace(producer1, InvocationKind.EXACTLY_ONCE) + callsInPlace(producer2, InvocationKind.EXACTLY_ONCE) + callsInPlace(producer3, InvocationKind.EXACTLY_ONCE) + callsInPlace(producer4, InvocationKind.EXACTLY_ONCE) + callsInPlace(producer5, InvocationKind.EXACTLY_ONCE) + callsInPlace(transform, InvocationKind.AT_MOST_ONCE) + } + + val result1 = producer1() + val result2 = producer2() + val result3 = producer3() + val result4 = producer4() + val result5 = producer5() + + return if ( + result1 is Ok && + result2 is Ok && + result3 is Ok && + result4 is Ok && + result5 is Ok + ) { + val transformed = transform( + result1.value, + result2.value, + result3.value, + result4.value, + result5.value + ) + + Ok(transformed) + } else { + val errors = listOf( + result1, + result2, + result3, + result4, + result5 + ).mapNotNull { it.getError() } + + Err(errors) + } +} diff --git a/kotlin-result/src/commonTest/kotlin/com/github/michaelbull/result/ZipTest.kt b/kotlin-result/src/commonTest/kotlin/com/github/michaelbull/result/ZipTest.kt index 56374cf..b1ac10d 100644 --- a/kotlin-result/src/commonTest/kotlin/com/github/michaelbull/result/ZipTest.kt +++ b/kotlin-result/src/commonTest/kotlin/com/github/michaelbull/result/ZipTest.kt @@ -199,4 +199,73 @@ class ZipTest { ) } } + + class ZipOrAccumulate { + + @Test + fun returnsTransformedValueIfAllOk() { + val result = zipOrAccumulate( + { Ok(10) }, + { Ok(20) }, + { Ok(30) }, + { Ok(40) }, + { Ok(50) }, + ) { a, b, c, d, e -> + a + b + c + d + e + } + + result as Ok + + assertEquals( + expected = 150, + actual = result.value, + ) + } + + @Test + fun returnsAllErrsIfAllErr() { + val result = zipOrAccumulate( + { Ok(10).and(Err("error one")) }, + { Ok(20).and(Err("error two")) }, + { Ok(30).and(Err("error three")) }, + { Ok(40).and(Err("error four")) }, + { Ok(50).and(Err("error five")) }, + ) { a, b, c, d, e -> + a + b + c + d + e + } + + result as Err + + assertEquals( + expected = listOf( + "error one", + "error two", + "error three", + "error four", + "error five", + ), + actual = result.error, + ) + } + + @Test + fun returnsOneErrsIfOneOfErr() { + val result = zipOrAccumulate( + { Ok(10) }, + { Ok(20).and(Err("only error")) }, + { Ok(30) }, + { Ok(40) }, + { Ok(50) }, + ) { a, b, c, d, e -> + a + b + c + d + e + } + + result as Err + + assertEquals( + expected = listOf("only error",), + actual = result.error, + ) + } + } }