ApplicativeError

ApplicativeError is the typeclase used to explicitly represent errors during independent computations. It is parametrized to an error type E, which means the datatype has at least a “success” and a “failure” version.

These errors can come in the form of Throwable, Exception, or any other type that is more relevant to the domain; like for example a sealed class UserNotFoundReason that contains 3 inheritors.

Some of the datatypes Λrrow provides can have these error types already fixed. That’s the case of Try<A>, which has its error type fixed to Throwable. Other datatypes like Either<E, A> allow for the user to apply their error type of choice.

Main Combinators

ApplicativeError inherits all the combinators available in Applicative. It also adds several of its own.

raiseError

A constructor function. It lifts an exception into the computational context of a type constructor.

import arrow.*
import arrow.core.*

Either.applicativeError<Throwable>().raiseError<Int>(RuntimeException("Paco"))
// Left(a=java.lang.RuntimeException: Paco)
import arrow.data.*

Try.applicativeError().raiseError<Int>(RuntimeException("Paco"))
// Failure(exception=java.lang.RuntimeException: Paco)
import arrow.effects.*

IO.applicativeError().raiseError<Int>(RuntimeException("Paco"))
// RaiseError(exception=java.lang.RuntimeException: Paco)

handleErrorWith

This method requires a function that creates a new datatype from an error, (E) -> Kind<F, A>. This function is used as a catch + recover clause for the current instance, allowing it to return a new computation after a failure.

If Monad has flatMap to allow mapping the value inside a successful datatype into a new datatype, you can think of handleErrorWith as a way that allows you to map the value of a *failed datatype into a new datatype.

val AE_EITHER = Either.applicativeError<Throwable>()

val success: Either<Throwable, Int> = Either.Right(1)

AE_EITHER.handleErrorWith(success, { t -> Either.Right(0) })
// Right(b=1)
val failure: Either<Throwable, Int> = Either.Left(RuntimeException("Boom!"))

AE_EITHER.handleErrorWith(failure, { t -> Either.Right(0) })
// Right(b=0)

handleError

Similar to handleErrorWith, except the function can return any regular value. This value will be wrapped and used as a return.

AE_EITHER.handleError(success, { t -> 0 })
// Right(b=1)
AE_EITHER.handleError(failure, { t -> 0 })
// Right(b=0)

attempt

Maps the current content of the datatype to an Either<E, A>, recovering from any previous error state.

val AE_TRY = Try.applicativeError()

val pass = Try { "3".toInt() }
pass
// Success(value=3)
AE_TRY.attempt(pass)
// Success(value=Right(b=3))
val fail = Try { "nope".toInt() }
fail
// Failure(exception=java.lang.NumberFormatException: For input string: "nope")
AE_TRY.attempt(fail)
// Success(value=Left(a=java.lang.NumberFormatException: For input string: "nope"))

fromEither

Constructor function from an Either<E, A> to the current datatype.

AE_TRY.fromEither(Either.Right(1))
// Success(value=1)
AE_TRY.fromEither(Either.Left(RuntimeException("Boom")))
// Failure(exception=java.lang.RuntimeException: Boom)

catch

Constructor function. It takes two function parameters. The first is a generator function from () -> A. The second is an error mapping function from (Throwable) -> E. catch() runs the generator function to generate a success datatype, and if it throws an exception it uses the error mapping function to create a new failure datatype.

AE_EITHER.catch({ 1 } ,{ it })
// Right(b=1)
AE_EITHER.catch({ throw RuntimeException("Boom") } ,{ it })
// Left(a=java.lang.RuntimeException: Boom)

Laws

Arrow provides ApplicativeErrorLaws in the form of test cases for internal verification of lawful instances and third party apps creating their own ApplicativeError instances.

Data types

The following datatypes in Arrow provide instances that adhere to the ApplicativeError typeclass.