Add zipOrAccumulate

Closes #89, closes #90
This commit is contained in:
YuitoSato 2023-07-30 18:52:16 +09:00 committed by Michael Bull
parent e81f581436
commit 27f0a63847
2 changed files with 299 additions and 42 deletions

View File

@ -12,18 +12,18 @@ private typealias Producer<T, E> = () -> Result<T, E>
* - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map2 * - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map2
*/ */
public inline fun <T1, T2, E, V> zip( public inline fun <T1, T2, E, V> zip(
result1: Producer<T1, E>, producer1: Producer<T1, E>,
result2: Producer<T2, E>, producer2: Producer<T2, E>,
transform: (T1, T2) -> V transform: (T1, T2) -> V
): Result<V, E> { ): Result<V, E> {
contract { contract {
callsInPlace(result1, InvocationKind.EXACTLY_ONCE) callsInPlace(producer1, InvocationKind.EXACTLY_ONCE)
callsInPlace(result2, InvocationKind.AT_MOST_ONCE) callsInPlace(producer2, InvocationKind.AT_MOST_ONCE)
callsInPlace(transform, InvocationKind.AT_MOST_ONCE) callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
} }
return result1().flatMap { v1 -> return producer1().flatMap { v1 ->
result2().map { v2 -> producer2().map { v2 ->
transform(v1, v2) transform(v1, v2)
} }
} }
@ -36,21 +36,21 @@ public inline fun <T1, T2, E, V> zip(
* - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map3 * - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map3
*/ */
public inline fun <T1, T2, T3, E, V> zip( public inline fun <T1, T2, T3, E, V> zip(
result1: Producer<T1, E>, producer1: Producer<T1, E>,
result2: Producer<T2, E>, producer2: Producer<T2, E>,
result3: Producer<T3, E>, producer3: Producer<T3, E>,
transform: (T1, T2, T3) -> V transform: (T1, T2, T3) -> V
): Result<V, E> { ): Result<V, E> {
contract { contract {
callsInPlace(result1, InvocationKind.EXACTLY_ONCE) callsInPlace(producer1, InvocationKind.EXACTLY_ONCE)
callsInPlace(result2, InvocationKind.AT_MOST_ONCE) callsInPlace(producer2, InvocationKind.AT_MOST_ONCE)
callsInPlace(result3, InvocationKind.AT_MOST_ONCE) callsInPlace(producer3, InvocationKind.AT_MOST_ONCE)
callsInPlace(transform, InvocationKind.AT_MOST_ONCE) callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
} }
return result1().flatMap { v1 -> return producer1().flatMap { v1 ->
result2().flatMap { v2 -> producer2().flatMap { v2 ->
result3().map { v3 -> producer3().map { v3 ->
transform(v1, v2, v3) transform(v1, v2, v3)
} }
} }
@ -64,24 +64,24 @@ public inline fun <T1, T2, T3, E, V> zip(
* - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map4 * - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map4
*/ */
public inline fun <T1, T2, T3, T4, E, V> zip( public inline fun <T1, T2, T3, T4, E, V> zip(
result1: Producer<T1, E>, producer1: Producer<T1, E>,
result2: Producer<T2, E>, producer2: Producer<T2, E>,
result3: Producer<T3, E>, producer3: Producer<T3, E>,
result4: Producer<T4, E>, producer4: Producer<T4, E>,
transform: (T1, T2, T3, T4) -> V transform: (T1, T2, T3, T4) -> V
): Result<V, E> { ): Result<V, E> {
contract { contract {
callsInPlace(result1, InvocationKind.EXACTLY_ONCE) callsInPlace(producer1, InvocationKind.EXACTLY_ONCE)
callsInPlace(result2, InvocationKind.AT_MOST_ONCE) callsInPlace(producer2, InvocationKind.AT_MOST_ONCE)
callsInPlace(result3, InvocationKind.AT_MOST_ONCE) callsInPlace(producer3, InvocationKind.AT_MOST_ONCE)
callsInPlace(result4, InvocationKind.AT_MOST_ONCE) callsInPlace(producer4, InvocationKind.AT_MOST_ONCE)
callsInPlace(transform, InvocationKind.AT_MOST_ONCE) callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
} }
return result1().flatMap { v1 -> return producer1().flatMap { v1 ->
result2().flatMap { v2 -> producer2().flatMap { v2 ->
result3().flatMap { v3 -> producer3().flatMap { v3 ->
result4().map { v4 -> producer4().map { v4 ->
transform(v1, v2, v3, v4) transform(v1, v2, v3, v4)
} }
} }
@ -96,27 +96,27 @@ public inline fun <T1, T2, T3, T4, E, V> zip(
* - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map5 * - Elm: http://package.elm-lang.org/packages/elm-lang/core/latest/Result#map5
*/ */
public inline fun <T1, T2, T3, T4, T5, E, V> zip( public inline fun <T1, T2, T3, T4, T5, E, V> zip(
result1: Producer<T1, E>, producer1: Producer<T1, E>,
result2: Producer<T2, E>, producer2: Producer<T2, E>,
result3: Producer<T3, E>, producer3: Producer<T3, E>,
result4: Producer<T4, E>, producer4: Producer<T4, E>,
result5: Producer<T5, E>, producer5: Producer<T5, E>,
transform: (T1, T2, T3, T4, T5) -> V transform: (T1, T2, T3, T4, T5) -> V
): Result<V, E> { ): Result<V, E> {
contract { contract {
callsInPlace(result1, InvocationKind.EXACTLY_ONCE) callsInPlace(producer1, InvocationKind.EXACTLY_ONCE)
callsInPlace(result2, InvocationKind.AT_MOST_ONCE) callsInPlace(producer2, InvocationKind.AT_MOST_ONCE)
callsInPlace(result3, InvocationKind.AT_MOST_ONCE) callsInPlace(producer3, InvocationKind.AT_MOST_ONCE)
callsInPlace(result4, InvocationKind.AT_MOST_ONCE) callsInPlace(producer4, InvocationKind.AT_MOST_ONCE)
callsInPlace(result5, InvocationKind.AT_MOST_ONCE) callsInPlace(producer5, InvocationKind.AT_MOST_ONCE)
callsInPlace(transform, InvocationKind.AT_MOST_ONCE) callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
} }
return result1().flatMap { v1 -> return producer1().flatMap { v1 ->
result2().flatMap { v2 -> producer2().flatMap { v2 ->
result3().flatMap { v3 -> producer3().flatMap { v3 ->
result4().flatMap { v4 -> producer4().flatMap { v4 ->
result5().map { v5 -> producer5().map { v5 ->
transform(v1, v2, v3, v4, v5) transform(v1, v2, v3, v4, v5)
} }
} }
@ -124,3 +124,191 @@ public inline fun <T1, T2, T3, T4, T5, E, V> 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 <T1, T2, E, V> zipOrAccumulate(
producer1: () -> Result<T1, E>,
producer2: () -> Result<T2, E>,
transform: (T1, T2) -> V,
): Result<V, List<E>> {
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 <T1, T2, T3, E, V> zipOrAccumulate(
producer1: () -> Result<T1, E>,
producer2: () -> Result<T2, E>,
producer3: () -> Result<T3, E>,
transform: (T1, T2, T3) -> V,
): Result<V, List<E>> {
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 <T1, T2, T3, T4, E, V> zipOrAccumulate(
producer1: () -> Result<T1, E>,
producer2: () -> Result<T2, E>,
producer3: () -> Result<T3, E>,
producer4: () -> Result<T4, E>,
transform: (T1, T2, T3, T4) -> V,
): Result<V, Collection<E>> {
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 <T1, T2, T3, T4, T5, E, V> zipOrAccumulate(
producer1: () -> Result<T1, E>,
producer2: () -> Result<T2, E>,
producer3: () -> Result<T3, E>,
producer4: () -> Result<T4, E>,
producer5: () -> Result<T5, E>,
transform: (T1, T2, T3, T4, T5) -> V,
): Result<V, Collection<E>> {
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)
}
}

View File

@ -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,
)
}
}
} }