Package-level declarations

Types

Link copied to clipboard
typealias EagerEffect<R, A> = Raise<R>.() -> A

The same behavior and API as Effect except without requiring suspend.

Link copied to clipboard
typealias Effect<R, A> = suspend Raise<R>.() -> A

Effect represents a function of suspend Raise<R>.() -> A, that short-circuit with a value of R or Throwable, or completes with a value of A.

So Effect is defined by suspend fun <B> fold(recover: suspend (Throwable) -> B, resolve: suspend (R) -> B, transform: suspend (A) -> B): B, to map all values of R, Throwable and A to a value of B.

#writing-a-program-with-effect #handling-errors #recover #catch #structured-concurrency #arrow-fx-coroutines #parzip #partraverse #racen #bracketcase--resource #kotlinx #withcontext #async #launch #strange-edge-cases

Writing a program with Effect

Let's write a small program to read a file from disk, and instead of having the program work exception based we want to turn it into a polymorphic type-safe program.

We'll start by defining a small function that accepts a String, and does some simply validation to check that the path is not empty. If the path is empty, we want to program to result in EmptyPath. So we're immediately going to see how we can raise an error of any arbitrary type R by using the function raise. The name raise comes raising an intterupt, or changing, especially unexpectedly, away from the computation and finishing the Continuation with R.

object EmptyPath

fun readFile(path: String): Effect<EmptyPath, Unit> = effect {
if (path.isEmpty()) raise(EmptyPath) else Unit
}

Here we see how we can define an Effect<R, A> which has EmptyPath for the raise type R, and Unit for the success type A.

Patterns like validating a Boolean is very common, and the Effect DSL offers utility functions like kotlin.require and kotlin.requireNotNull. They're named ensure and ensureNotNull to avoid conflicts with the kotlin namespace. So let's rewrite the function from above to use the DSL instead.

fun readFile2(path: String?): Effect<EmptyPath, Unit> = effect {
ensureNotNull(path) { EmptyPath }
ensure(path.isNotEmpty()) { EmptyPath }
}

Now that we have the path, we can read from the File and return it as a domain model Content. We also want to take a look at what exceptions reading from a file might occur FileNotFoundException&SecurityError, so lets make some domain errors for those too. Grouping them as a sealed interface is useful since that way we can resolve all errors in a type safe manner.

@JvmInline
value class Content(val body: List<String>)

sealed interface FileError
@JvmInline value class SecurityError(val msg: String?) : FileError
@JvmInline value class FileNotFound(val path: String) : FileError
object EmptyPath : FileError {
override fun toString() = "EmptyPath"
}

We can finish our function, but we need to refactor the return type from Unit to Content and the error type from EmptyPath to FileError.

fun readFile(path: String?): Effect<FileError, Content> = effect {
ensureNotNull(path) { EmptyPath }
ensure(path.isNotEmpty()) { EmptyPath }
try {
val lines = File(path).readLines()
Content(lines)
} catch (e: FileNotFoundException) {
raise(FileNotFound(path))
} catch (e: SecurityException) {
raise(SecurityError(e.message))
}
}

The readFile function defines a suspend fun that will return:

  • the Content of a given path

  • a FileError

  • An unexpected fatal error (OutOfMemoryException)

Since these are the properties of our Effect function, we can turn it into a value.

suspend fun main() {
readFile("").toEither() shouldBe Either.Left(EmptyPath)
readFile("knit.properties").toValidated() shouldBe Validated.Invalid(FileNotFound("knit.properties"))
readFile("gradle.properties").toIor() shouldBe Ior.Left(FileNotFound("gradle.properties"))
readFile("README.MD").toOption { None } shouldBe None

readFile("build.gradle.kts").fold({ _: FileError -> null }, { it })
.shouldBeInstanceOf<Content>()
.body.shouldNotBeEmpty()
}

The functions above are available out of the box, but it's easy to define your own extension functions in terms of fold. Implementing the toEither() operator is as simple as:

suspend fun <R, A> Effect<R, A>.toEither(): Either<R, A> =
fold({ Either.Left(it) }) { Either.Right(it) }

suspend fun <A> Effect<None, A>.toOption(): Option<A> =
fold(::identity) { Some(it) }

Adding your own syntax to Raise<R> is not advised, yet, but will be easy once "Multiple Receivers" become available.

context(Raise<R>)
suspend fun <R, A> Either<R, A>.bind(): A =
when (this) {
is Either.Left -> raise(value)
is Either.Right -> value
}

context(Raise<None>)
fun <A> Option<A>.bind(): A =
fold({ raise(it) }, ::identity)

Handling errors

An Effect has 2 error channels: Throwable and R There are two separate handlers to transform either of the error channels.

  • recover to handle, and transform any error of type R.

  • catch to handle, and transform and error of type Throwable.

recover

recover handles the error of type R, by providing a new value of type A, raising a different error of type E, or throwing an exception.

Let's take a look at some examples:

We define a val failed of type Effect<String, Int>, that represents a failed effect with value "failed".

val failed: Effect<String, Int> =
effect { raise("failed") }

We can recover the failure, and resolve it by providing a default value of -1 or the length of the error: String.

val default: Effect<Nothing, Int> =
failed.recover { -1 }

val resolved: Effect<Nothing, Int> =
failed.recover { it.length }

As you can see the resulting error is now of type Nothing, since we did not raise any new errors. So our Effect knows that no short-circuiting will occur during execution. Awesome! But it can also infer to any other error type that you might want instead, because it's never going to occur. So as you see below, we can even assign our Effect<Nothing, A> to Effect<E, A>, where E can be any type.

val default2: Effect<Double, Int> = default
val resolved2: Effect<Unit, Int> = resolved

recover also allows us to change the error type when we resolve the error of type R. Below we handle our error of String and turn it into List<Char> using reversed().toList(). This is a powerful operation, since it allows us to transform our error types across boundaries or layers.

val newError: Effect<List<Char>, Int> =
failed.recover { str ->
raise(str.reversed().toList())
}

Finally, since recover supports suspend we can safely call other suspend code and throw Throwable into the suspend system. This is typically undesired, since you should prefer lifting Throwable into typed values of R to make them compile-time tracked.

val newException: Effect<Nothing, Int> =
failed.recover { str -> throw RuntimeException(str) }

catch

catch gives us the same powers as recover, but instead of resolving R we're recovering from any unexpected Throwable. Unexpected, because the expectation is that all Throwable get turned into R unless it's a fatal/unexpected. This operator is useful when you need to work/wrap foreign code, especially Java SDKs or any code that is heavily based on exceptions.

Below we've defined a foreign value that represents wrapping a foreign API which might throw RuntimeException.

val foreign = effect<String, Int> {
throw RuntimeException("BOOM!")
}

We can catch to run the effect recovering from any exception, and recover it by providing a default value of -1 or the length of the Throwable.message.

val default3: Effect<String, Int> =
foreign.catch { -1 }

val resolved3: Effect<String, Int> =
foreign.catch { it.message?.length ?: -1 }

A big difference with recover is that catch cannot change the error type of R because it doesn't resolve it, so it stays unchanged. You can however compose recover, and v to resolve the error type and recover the exception.

val default4: Effect<Nothing, Int> =
foreign
.recover<String, Nothing, Int> { -1 }
.catch { -2 }

catch however offers an overload that can refine the exception. Let's say you're wrapping some database interactions that might throw java.sql.SqlException, or org.postgresql.util.PSQLException, then you might only be interested in those exceptions and not Throwable. catch allows you to install multiple handlers for specific exceptions. If the desired exception is not matched, then it stays in the suspend exception channel and will be thrown or recovered at a later point.

val default5: Effect<String, Int> =
foreign
.catch { ex: RuntimeException -> -1 }
.catch { ex: java.sql.SQLException -> -2 }

Finally, since catch also supports suspend we can safely call other suspend code and throw Throwable into the suspend system. This can be useful if refinement of exceptions is not sufficient, for example in the case of org.postgresql.util.PSQLException you might want to check the SQLState to check for a foreign key violation and rethrow the exception if not matched.

suspend fun java.sql.SQLException.isForeignKeyViolation(): Boolean = true

val rethrown: Effect<String, Int> =
failed.catch { ex: java.sql.SQLException ->
if(ex.isForeignKeyViolation()) raise("foreign key violation")
else throw ex
}

Note: Handling errors can also be done with try/catch but this is not recommended, it uses CancellationException which is used to cancel Coroutines and is advised not to capture in Kotlin. The CancellationException from Effect is RaiseCancellationException, this a public type, thus can be distinguished from any other CancellationException if necessary.

Structured Concurrency

Effect<R, A> relies on kotlin.cancellation.CancellationException to raise error values of type R inside the Continuation since it effectively cancels/short-circuits it. For this reason raise adheres to the same rules as Structured Concurrency

Let's overview below how raise behaves with the different concurrency builders from Arrow Fx & KotlinX Coroutines. In the examples below we're going to be using a utility to show how sibling tasks get cancelled. The utility function show below called awaitExitCase will never finish suspending, and completes a Deferred with the ExitCase. ExitCase is a sealed class that can be a value of Failure(Throwable), Cancelled(CancellationException), or Completed. Since awaitExitCase suspends forever, it can only result in Cancelled(CancellationException).

suspend fun <A> awaitExitCase(exit: CompletableDeferred<ExitCase>): A =
guaranteeCase(::awaitCancellation) { exitCase -> exit.complete(exitCase) }

Arrow Fx Coroutines

All operators in Arrow Fx Coroutines run in place, so they have no way of leaking raise. It's there always safe to compose effect with any Arrow Fx combinator. Let's see some small examples below.

parZip

 suspend fun main() {
val error = "Error"
val exit = CompletableDeferred<ExitCase>()
effect<String, Int> {
parZip({ awaitExitCase<Int>(exit) }, { raise(error) }) { a: Int, b: Int -> a + b }
}.fold({ it shouldBe error }, { fail("Int can never be the result") })
exit.await().shouldBeTypeOf<ExitCase>()
}

parTraverse

suspend fun main() {
val error = "Error"
val exits = (0..3).map { CompletableDeferred<ExitCase>() }
effect<String, List<Unit>> {
(0..4).parTraverse { index ->
if (index == 4) raise(error)
else awaitExitCase(exits[index])
}
}.fold({ msg -> msg shouldBe error }, { fail("Int can never be the result") })
// It's possible not all parallel task got launched, and in those cases awaitCancellation never ran
exits.forEach { exit -> exit.getOrNull()?.shouldBeTypeOf<ExitCase.Cancelled>() }
}

parTraverse will launch 5 tasks, for every element in 1..5. The last task to get scheduled will raise with "error", and it will cancel the other launched tasks before returning.

raceN

suspend fun main() {
val error = "Error"
val exit = CompletableDeferred<ExitCase>()
effect<String, Int> {
raceN({ awaitExitCase<Int>(exit) }) { raise(error) }
.merge() // Flatten Either<Int, Int> result from race into Int
}.fold({ msg -> msg shouldBe error }, { fail("Int can never be the result") })
// It's possible not all parallel task got launched, and in those cases awaitCancellation never ran
exit.getOrNull()?.shouldBeTypeOf<ExitCase.Cancelled>()
}

raceN races n suspend functions in parallel, and cancels all participating functions when a winner is found. We can consider the function that raises the winner of the race, except with a raised value instead of a successful one. So when a function in the race raises, and thus short-circuiting the race, it will cancel all the participating functions.

bracketCase / Resource

suspend fun main() {
val error = "Error"
val exit = CompletableDeferred<ExitCase>()
effect<String, Int> {
bracketCase(
acquire = { File("build.gradle.kts").bufferedReader() },
use = { reader: BufferedReader -> raise(error) },
release = { reader, exitCase ->
reader.close()
exit.complete(exitCase)
}
)
}.fold({ it shouldBe error }, { fail("Int can never be the result") })
exit.await().shouldBeTypeOf<ExitCase.Cancelled>()
}
suspend fun main() {
val error = "Error"
val exit = CompletableDeferred<ExitCase>()

suspend fun ResourceScope.bufferedReader(path: String): BufferedReader =
autoCloseable { File(path).bufferedReader() }.also {
onRelease { exitCase -> exit.complete(exitCase) }
}

resourceScope {
effect<String, Int> {
val reader = bufferedReader("build.gradle.kts")
raise(error)
reader.lineSequence().count()
}.fold({ it shouldBe error }, { fail("Int can never be the result") })
}
exit.await().shouldBeTypeOf<ExitCase.Cancelled>()
}

KotlinX

withContext

It's always safe to call raise from withContext since it runs in place, so it has no way of leaking raise. When raise is called from within withContext it will cancel all Jobs running inside the CoroutineScope of withContext.

Link copied to clipboard
annotation class RaiseDSL
Link copied to clipboard

Functions

Link copied to clipboard
fun <E, A> Effect<E, A>.catch(): Effect<E, Result<A>>

Runs the Effect and captures any nonFatalOrThrow exception into Result.

@JvmName(name = "catchReified")
infix inline fun <T : Throwable, E, A> EagerEffect<E, A>.catch(crossinline recover: Raise<E>.(T) -> A): EagerEffect<E, A>
infix fun <E, A> EagerEffect<E, A>.catch(recover: Raise<E>.(throwable: Throwable) -> A): EagerEffect<E, A>
@JvmName(name = "catchReified")
inline fun <T : Throwable, R, A> Raise<R>.catch(action: Raise<R>.() -> A, catch: Raise<R>.(T) -> A): A
inline fun <R, A> Raise<R>.catch(action: Raise<R>.() -> A, catch: Raise<R>.(Throwable) -> A): A

@JvmName(name = "catchReified")
infix inline fun <T : Throwable, E, A> Effect<E, A>.catch(crossinline recover: suspend Raise<E>.(T) -> A): Effect<E, A>

A version of catch that refines the Throwable to T. This is useful for wrapping foreign code, such as database, network calls, etc.

infix fun <E, A> Effect<E, A>.catch(resolve: suspend Raise<E>.(throwable: Throwable) -> A): Effect<E, A>

Catch any unexpected exceptions, and resolve them. You can either return a value a new value of A, or short-circuit the effect by raising with a value of E, or raise an exception into suspend.

Link copied to clipboard
inline fun <R, A> eagerEffect(noinline block: Raise<R>.() -> A): EagerEffect<R, A>
Link copied to clipboard
inline fun <R, A> effect(noinline block: suspend Raise<R>.() -> A): Effect<R, A>
Link copied to clipboard
inline fun <E, A> either(block: Raise<E>.() -> A): Either<E, A>
Link copied to clipboard
inline fun <R> Raise<R>.ensure(condition: Boolean, raise: () -> R)
Link copied to clipboard
inline fun <R, B : Any> Raise<R>.ensureNotNull(value: B?, raise: () -> R): B
Link copied to clipboard
inline fun <R, A, B> EagerEffect<R, A>.fold(recover: (R) -> B, transform: (A) -> B): B
suspend fun <R, A, B> Effect<R, A>.fold(recover: suspend (raised: R) -> B, transform: suspend (value: A) -> B): B
@JvmName(name = "_foldOrThrow")
inline fun <R, A, B> fold(program: Raise<R>.() -> A, recover: (raised: R) -> B, transform: (value: A) -> B): B
inline fun <R, A, B> EagerEffect<R, A>.fold(error: (error: Throwable) -> B, recover: (raised: R) -> B, transform: (value: A) -> B): B
@JvmName(name = "_fold")
inline fun <R, A, B> fold(program: Raise<R>.() -> A, error: (error: Throwable) -> B, recover: (raised: R) -> B, transform: (value: A) -> B): B

suspend fun <R, A, B> Effect<R, A>.fold(error: suspend (error: Throwable) -> B, recover: suspend (raised: R) -> B, transform: suspend (value: A) -> B): B

invoke the Effect and fold the result:

Link copied to clipboard
inline fun <E, A> ior(semigroup: Semigroup<E>, block: IorRaise<E>.() -> A): Ior<E, A>
Link copied to clipboard
inline fun <R, A> Raise<NonEmptyList<R>>.mapErrorNel(crossinline block: Raise<R>.() -> A): A

Re-raise any errors in block in a NonEmptyList.

Link copied to clipboard
inline fun <R, A, B> Raise<NonEmptyList<R>>.mapOrAccumulate(list: Iterable<A>, crossinline block: Raise<R>.(A) -> B): List<B>

Accumulate the errors obtained by executing the block over every element of list.

inline fun <R, A, B> Raise<R>.mapOrAccumulate(semigroup: Semigroup<@UnsafeVariance R>, list: Iterable<A>, block: Raise<R>.(A) -> B): List<B>

Accumulate the errors obtained by executing the block over every element of list using the given semigroup.

Link copied to clipboard
fun <A> EagerEffect<A, A>.merge(): A
suspend fun <A> Effect<A, A>.merge(): A
Link copied to clipboard
inline fun <A> nullable(block: NullableRaise.() -> A): A?
Link copied to clipboard
inline fun <A> option(block: OptionRaise.() -> A): Option<A>
Link copied to clipboard
fun <E, A> EagerEffect<E, A>.orNull(): A?

suspend fun <E, A> Effect<E, A>.orNull(): A?

Run the Effect by returning A, or null if raised with E.

Link copied to clipboard
infix fun <E, E2, A> EagerEffect<E, A>.recover(resolve: Raise<E2>.(raised: E) -> A): EagerEffect<E2, A>
inline fun <R, E, A> Raise<R>.recover(action: Raise<E>.() -> A, recover: Raise<R>.(E) -> A, catch: Raise<R>.(Throwable) -> A): A

infix fun <E, E2, A> Effect<E, A>.recover(resolve: suspend Raise<E2>.(raised: E) -> A): Effect<E2, A>

Catch the raised value E of the Effect. You can either return a value a new value of A, or short-circuit the effect by raising with a value of E, or raise an exception into suspend.

inline fun <R, E, A> Raise<R>.recover(action: Raise<E>.() -> A, recover: Raise<R>.(E) -> A): A

Execute the Raise context function resulting in A or any logical error of type E, and recover by providing a fallback value of type A or raising a new error of type R.

Link copied to clipboard
inline fun <A> result(block: ResultRaise.() -> A): Result<A>
Link copied to clipboard
fun <E, A> EagerEffect<E, A>.toEither(): Either<E, A>

suspend fun <E, A> Effect<E, A>.toEither(): Either<E, A>

Run the Effect by returning Either.Right of A, or Either.Left of E.

Link copied to clipboard
fun <E, A> EagerEffect<E, A>.toIor(): Ior<E, A>

suspend fun <E, A> Effect<E, A>.toIor(): Ior<E, A>

Run the Effect by returning Ior.Right of A, or Ior.Left of E.

Link copied to clipboard
fun <E, A> EagerEffect<E, A>.toOption(orElse: (E) -> Option<A>): Option<A>

suspend fun <A> Effect<None, A>.toOption(): Option<A>

Run the Effect by returning Option of A, or None if raised with None.

suspend fun <E, A> Effect<E, A>.toOption(orElse: suspend (E) -> Option<A>): Option<A>

Run the Effect by returning Option of A, orElse run the fallback lambda and returning its result of Option of A.

Link copied to clipboard
fun <E, A> EagerEffect<E, A>.toResult(orElse: (E) -> Result<A>): Result<A>

suspend fun <A> Effect<Throwable, A>.toResult(): Result<A>

Run the Effect by returning Result of A, or Result.Failure if raised with Throwable.

suspend fun <E, A> Effect<E, A>.toResult(orElse: suspend (E) -> Result<A>): Result<A>

Run the Effect by returning Result of A, orElse run the fallback lambda and returning its result of Result of A.

Link copied to clipboard

suspend fun <E, A> Effect<E, A>.toValidated(): Validated<E, A>

Run the Effect by returning Validated.Valid of A, or Validated.Invalid of E.

Link copied to clipboard
inline fun <R, A, B, C> Raise<NonEmptyList<R>>.zipOrAccumulate(crossinline action1: Raise<R>.() -> A, crossinline action2: Raise<R>.() -> B, crossinline block: Raise<R>.(A, B) -> C): C

Accumulate the errors from running both action1 and action2.

inline fun <R, A, B, C> Raise<R>.zipOrAccumulate(semigroup: Semigroup<@UnsafeVariance R>, action1: Raise<R>.() -> A, action2: Raise<R>.() -> B, block: Raise<R>.(A, B) -> C): C

Accumulate the errors from running both action1 and action2 using the given semigroup.

inline fun <R, A, B, C, D> Raise<NonEmptyList<R>>.zipOrAccumulate(crossinline action1: Raise<R>.() -> A, crossinline action2: Raise<R>.() -> B, crossinline action3: Raise<R>.() -> C, crossinline block: Raise<R>.(A, B, C) -> D): D

Accumulate the errors from running action1, action2, and action3.

inline fun <R, A, B, C, D> Raise<R>.zipOrAccumulate(semigroup: Semigroup<@UnsafeVariance R>, action1: Raise<R>.() -> A, action2: Raise<R>.() -> B, action3: Raise<R>.() -> C, block: Raise<R>.(A, B, C) -> D): D

Accumulate the errors from running action1, action2, and action3 using the given semigroup.

inline fun <R, A, B, C, D, E> Raise<NonEmptyList<R>>.zipOrAccumulate(crossinline action1: Raise<R>.() -> A, crossinline action2: Raise<R>.() -> B, crossinline action3: Raise<R>.() -> C, crossinline action4: Raise<R>.() -> D, crossinline block: Raise<R>.(A, B, C, D) -> E): E

Accumulate the errors from running action1, action2, action3, and action4.

inline fun <R, A, B, C, D, E> Raise<R>.zipOrAccumulate(semigroup: Semigroup<@UnsafeVariance R>, action1: Raise<R>.() -> A, action2: Raise<R>.() -> B, action3: Raise<R>.() -> C, action4: Raise<R>.() -> D, block: Raise<R>.(A, B, C, D) -> E): E

Accumulate the errors from running action1, action2, action3, and action4 using the given semigroup.

inline fun <R, A, B, C, D, E, F> Raise<NonEmptyList<R>>.zipOrAccumulate(crossinline action1: Raise<R>.() -> A, crossinline action2: Raise<R>.() -> B, crossinline action3: Raise<R>.() -> C, crossinline action4: Raise<R>.() -> D, crossinline action5: Raise<R>.() -> E, crossinline block: Raise<R>.(A, B, C, D, E) -> F): F

Accumulate the errors from running action1, action2, action3, action4, and action5.

inline fun <R, A, B, C, D, E, F> Raise<R>.zipOrAccumulate(semigroup: Semigroup<@UnsafeVariance R>, action1: Raise<R>.() -> A, action2: Raise<R>.() -> B, action3: Raise<R>.() -> C, action4: Raise<R>.() -> D, action5: Raise<R>.() -> E, block: Raise<R>.(A, B, C, D, E) -> F): F

Accumulate the errors from running action1, action2, action3, action4, and action5 using the given semigroup.

inline fun <R, A, B, C, D, E, F, G> Raise<NonEmptyList<R>>.zipOrAccumulate(crossinline action1: Raise<R>.() -> A, crossinline action2: Raise<R>.() -> B, crossinline action3: Raise<R>.() -> C, crossinline action4: Raise<R>.() -> D, crossinline action5: Raise<R>.() -> E, crossinline action6: Raise<R>.() -> F, crossinline block: Raise<R>.(A, B, C, D, E, F) -> G): G

Accumulate the errors from running action1, action2, action3, action4, action5, and action6.

inline fun <R, A, B, C, D, E, F, G> Raise<R>.zipOrAccumulate(semigroup: Semigroup<@UnsafeVariance R>, action1: Raise<R>.() -> A, action2: Raise<R>.() -> B, action3: Raise<R>.() -> C, action4: Raise<R>.() -> D, action5: Raise<R>.() -> E, action6: Raise<R>.() -> F, block: Raise<R>.(A, B, C, D, E, F) -> G): G

Accumulate the errors from running action1, action2, action3, action4, action5, and action6 using the given semigroup.

inline fun <R, A, B, C, D, E, F, G, H> Raise<NonEmptyList<R>>.zipOrAccumulate(crossinline action1: Raise<R>.() -> A, crossinline action2: Raise<R>.() -> B, crossinline action3: Raise<R>.() -> C, crossinline action4: Raise<R>.() -> D, crossinline action5: Raise<R>.() -> E, crossinline action6: Raise<R>.() -> F, crossinline action7: Raise<R>.() -> G, crossinline block: Raise<R>.(A, B, C, D, E, F, G) -> H): H

Accumulate the errors from running action1, action2, action3, action4, action5, action6, and action7.

inline fun <R, A, B, C, D, E, F, G, H> Raise<R>.zipOrAccumulate(semigroup: Semigroup<@UnsafeVariance R>, action1: Raise<R>.() -> A, action2: Raise<R>.() -> B, action3: Raise<R>.() -> C, action4: Raise<R>.() -> D, action5: Raise<R>.() -> E, action6: Raise<R>.() -> F, action7: Raise<R>.() -> G, block: Raise<R>.(A, B, C, D, E, F, G) -> H): H

Accumulate the errors from running action1, action2, action3, action4, action5, action6, and action7 using the given semigroup.

inline fun <R, A, B, C, D, E, F, G, H, I> Raise<NonEmptyList<R>>.zipOrAccumulate(crossinline action1: Raise<R>.() -> A, crossinline action2: Raise<R>.() -> B, crossinline action3: Raise<R>.() -> C, crossinline action4: Raise<R>.() -> D, crossinline action5: Raise<R>.() -> E, crossinline action6: Raise<R>.() -> F, crossinline action7: Raise<R>.() -> G, crossinline action8: Raise<R>.() -> H, crossinline block: Raise<R>.(A, B, C, D, E, F, G, H) -> I): I

Accumulate the errors from running action1, action2, action3, action4, action5, action6, action7, and action8.

inline fun <R, A, B, C, D, E, F, G, H, I> Raise<R>.zipOrAccumulate(semigroup: Semigroup<@UnsafeVariance R>, action1: Raise<R>.() -> A, action2: Raise<R>.() -> B, action3: Raise<R>.() -> C, action4: Raise<R>.() -> D, action5: Raise<R>.() -> E, action6: Raise<R>.() -> F, action7: Raise<R>.() -> G, action8: Raise<R>.() -> H, block: Raise<R>.(A, B, C, D, E, F, G, H) -> I): I

Accumulate the errors from running action1, action2, action3, action4, action5, action6, action7, and action8 using the given semigroup.

inline fun <R, A, B, C, D, E, F, G, H, I, J> Raise<NonEmptyList<R>>.zipOrAccumulate(crossinline action1: Raise<R>.() -> A, crossinline action2: Raise<R>.() -> B, crossinline action3: Raise<R>.() -> C, crossinline action4: Raise<R>.() -> D, crossinline action5: Raise<R>.() -> E, crossinline action6: Raise<R>.() -> F, crossinline action7: Raise<R>.() -> G, crossinline action8: Raise<R>.() -> H, crossinline action9: Raise<R>.() -> I, crossinline block: Raise<R>.(A, B, C, D, E, F, G, H, I) -> J): J

Accumulate the errors from running action1, action2, action3, action4, action5, action6, action7, action8, and action9.

inline fun <R, A, B, C, D, E, F, G, H, I, J> Raise<R>.zipOrAccumulate(semigroup: Semigroup<@UnsafeVariance R>, action1: Raise<R>.() -> A, action2: Raise<R>.() -> B, action3: Raise<R>.() -> C, action4: Raise<R>.() -> D, action5: Raise<R>.() -> E, action6: Raise<R>.() -> F, action7: Raise<R>.() -> G, action8: Raise<R>.() -> H, action9: Raise<R>.() -> I, block: Raise<R>.(A, B, C, D, E, F, G, H, I) -> J): J

Accumulate the errors from running action1, action2, action3, action4, action5, action6, action7, action8, and action9 using the given semigroup.