Either

sealed class Either<out A, out B>(source)

In day-to-day programming, it is fairly common to find ourselves writing functions that can fail. For instance, querying a service may result in a connection issue, or some unexpected JSON response.

To communicate these errors, it has become common practice to throw exceptions; however, exceptions are not tracked in any way, shape, or form by the compiler. To see what kind of exceptions (if any) a function may throw, we have to dig through the source code. Then, to handle these exceptions, we have to make sure we catch them at the call site. This all becomes even more unwieldy when we try to compose exception-throwing procedures.

import arrow.core.andThen

//sampleStart
val throwsSomeStuff: (Int) -> Double = {x -> x.toDouble()}
val throwsOtherThings: (Double) -> String = {x -> x.toString()}
val moreThrowing: (String) -> List<String> = {x -> listOf(x)}
val magic = throwsSomeStuff.andThen(throwsOtherThings).andThen(moreThrowing)
//sampleEnd
fun main() {
println ("magic = $magic")
}

Assume we happily throw exceptions in our code. Looking at the types of the functions above, any could throw a number of exceptions -- we do not know. When we compose, exceptions from any of the constituent functions can be thrown. Moreover, they may throw the same kind of exception (e.g., IllegalArgumentException) and, thus, it gets tricky tracking exactly where an exception came from.

How then do we communicate an error? By making it explicit in the data type we return.

import arrow.core.Either

val left: Either<String, Int> =
//sampleStart
Either.Left("Something went wrong")
//sampleEnd
fun main() {
println(left)
}

Because Either is right-biased, it is possible to define a Monad instance for it.

Since we only ever want the computation to continue in the case of Right (as captured by the right-bias nature), we fix the left type parameter and leave the right one free.

So, the map and flatMap methods are right-biased:

import arrow.core.Either
import arrow.core.flatMap

//sampleStart
val right: Either<String, Int> = Either.Right(5)
val value = right.flatMap{ Either.Right(it + 1) }
//sampleEnd
fun main() {
println("value = $value")
}
import arrow.core.Either
import arrow.core.flatMap

//sampleStart
val left: Either<String, Int> = Either.Left("Something went wrong")
val value = left.flatMap{ Either.Right(it + 1) }
//sampleEnd
fun main() {
println("value = $value")
}

Using Either instead of exceptions

As a running example, we will have a series of functions that will:

  • Parse a string into an integer

  • Calculate the reciprocal

  • Convert the reciprocal into a string

Using exception-throwing code, we could write something like this:

import arrow.core.Either
import arrow.core.flatMap

//sampleStart
fun parse(s: String): Int =
if (s.matches(Regex("-?[0-9]+"))) s.toInt()
else throw NumberFormatException("$s is not a valid integer.")

fun reciprocal(i: Int): Double =
if (i == 0) throw IllegalArgumentException("Cannot take reciprocal of 0.")
else 1.0 / i

fun stringify(d: Double): String = d.toString()
//sampleEnd

Instead, let's make the fact that some of our functions can fail explicit in the return type.

import arrow.core.Either
import arrow.core.flatMap
import arrow.core.left
import arrow.core.right

//sampleStart
// Either Style
fun parse(s: String): Either<NumberFormatException, Int> =
if (s.matches(Regex("-?[0-9]+"))) Either.Right(s.toInt())
else Either.Left(NumberFormatException("$s is not a valid integer."))

fun reciprocal(i: Int): Either<IllegalArgumentException, Double> =
if (i == 0) Either.Left(IllegalArgumentException("Cannot take reciprocal of 0."))
else Either.Right(1.0 / i)

fun stringify(d: Double): String = d.toString()

fun magic(s: String): Either<Exception, String> =
parse(s).flatMap { reciprocal(it) }.map { stringify(it) }
//sampleEnd

These calls to parse return a Left and Right value

import arrow.core.Either

fun parse(s: String): Either<NumberFormatException, Int> =
if (s.matches(Regex("-?[0-9]+"))) Either.Right(s.toInt())
else Either.Left(NumberFormatException("$s is not a valid integer."))

//sampleStart
val notANumber = parse("Not a number")
val number2 = parse("2")
//sampleEnd
fun main() {
println("notANumber = $notANumber")
println("number2 = $number2")
}

Now, using combinators like flatMap and map, we can compose our functions together.

import arrow.core.Either
import arrow.core.flatMap

fun parse(s: String): Either<NumberFormatException, Int> =
if (s.matches(Regex("-?[0-9]+"))) Either.Right(s.toInt())
else Either.Left(NumberFormatException("$s is not a valid integer."))

fun reciprocal(i: Int): Either<IllegalArgumentException, Double> =
if (i == 0) Either.Left(IllegalArgumentException("Cannot take reciprocal of 0."))
else Either.Right(1.0 / i)

fun stringify(d: Double): String = d.toString()

fun magic(s: String): Either<Exception, String> =
parse(s).flatMap{ reciprocal(it) }.map{ stringify(it) }

//sampleStart
val magic0 = magic("0")
val magic1 = magic("1")
val magicNotANumber = magic("Not a number")
//sampleEnd
fun main() {
println("magic0 = $magic0")
println("magic1 = $magic1")
println("magicNotANumber = $magicNotANumber")
}

In the following exercise, we pattern-match on every case in which the Either returned by magic can be in. Note the when clause in the Left - the compiler will complain if we leave that out because it knows that, given the type Either[Exception, String], there can be inhabitants of Left that are not NumberFormatException or IllegalArgumentException. You should also notice that we are using SmartCast for accessing Left and Right values.

import arrow.core.Either
import arrow.core.flatMap

fun parse(s: String): Either<NumberFormatException, Int> =
if (s.matches(Regex("-?[0-9]+"))) Either.Right(s.toInt())
else Either.Left(NumberFormatException("$s is not a valid integer."))

fun reciprocal(i: Int): Either<IllegalArgumentException, Double> =
if (i == 0) Either.Left(IllegalArgumentException("Cannot take reciprocal of 0."))
else Either.Right(1.0 / i)

fun stringify(d: Double): String = d.toString()

fun magic(s: String): Either<Exception, String> =
parse(s).flatMap{ reciprocal(it) }.map{ stringify(it) }

//sampleStart
val x = magic("2")
val value = when(x) {
is Either.Left -> when (x.value) {
is NumberFormatException -> "Not a number!"
is IllegalArgumentException -> "Can't take reciprocal of 0!"
else -> "Unknown error"
}
is Either.Right -> "Got reciprocal: ${x.value}"
}
//sampleEnd
fun main() {
println("value = $value")
}

Instead of using exceptions as our error value, let's instead enumerate explicitly the things that can go wrong in our program.

import arrow.core.Either
import arrow.core.flatMap
//sampleStart
// Either with ADT Style

sealed class Error {
object NotANumber : Error()
object NoZeroReciprocal : Error()
}

fun parse(s: String): Either<Error, Int> =
if (s.matches(Regex("-?[0-9]+"))) Either.Right(s.toInt())
else Either.Left(Error.NotANumber)

fun reciprocal(i: Int): Either<Error, Double> =
if (i == 0) Either.Left(Error.NoZeroReciprocal)
else Either.Right(1.0 / i)

fun stringify(d: Double): String = d.toString()

fun magic(s: String): Either<Error, String> =
parse(s).flatMap{reciprocal(it)}.map{ stringify(it) }
//sampleEnd

For our little module, we enumerate any and all errors that can occur. Then, instead of using exception classes as error values, we use one of the enumerated cases. Now, when we pattern match, we are able to comphrensively handle failure without resulting in an else branch; moreover, since Error is sealed, no outside code can add additional subtypes that we might fail to handle.

import arrow.core.Either
import arrow.core.flatMap

sealed class Error {
object NotANumber : Error()
object NoZeroReciprocal : Error()
}

fun parse(s: String): Either<Error, Int> =
if (s.matches(Regex("-?[0-9]+"))) Either.Right(s.toInt())
else Either.Left(Error.NotANumber)

fun reciprocal(i: Int): Either<Error, Double> =
if (i == 0) Either.Left(Error.NoZeroReciprocal)
else Either.Right(1.0 / i)

fun stringify(d: Double): String = d.toString()

fun magic(s: String): Either<Error, String> =
parse(s).flatMap{ reciprocal(it) }.map{ stringify(it) }

//sampleStart
val x = magic("2")
val value = when(x) {
is Either.Left -> when (x.value) {
is Error.NotANumber -> "Not a number!"
is Error.NoZeroReciprocal -> "Can't take reciprocal of 0!"
}
is Either.Right -> "Got reciprocal: ${x.value}"
}
//sampleEnd
fun main() {
println("value = $value")
}

Either.catch exceptions

Sometimes you do need to interact with code that can potentially throw exceptions. In such cases, you should mitigate the possibility that an exception can be thrown. You can do so by using the catch function.

Example:

import arrow.core.Either

//sampleStart
fun potentialThrowingCode(): String = throw RuntimeException("Blow up!")

suspend fun makeSureYourLogicDoesNotHaveSideEffects(): Either<Error, String> =
Either.catch { potentialThrowingCode() }.mapLeft { Error.SpecificError }
//sampleEnd
suspend fun main() {
println("makeSureYourLogicDoesNotHaveSideEffects().isLeft() = ${makeSureYourLogicDoesNotHaveSideEffects().isLeft()}")
}

sealed class Error {
object SpecificError : Error()
}

Syntax

Either can also map over the Left value with mapLeft, which is similar to map, but applies on left instances.

import arrow.core.Either

//sampleStart
val r : Either<Int, Int> = Either.Right(7)
val rightMapLeft = r.mapLeft {it + 1}
val l: Either<Int, Int> = Either.Left(7)
val leftMapLeft = l.mapLeft {it + 1}
//sampleEnd
fun main() {
println("rightMapLeft = $rightMapLeft")
println("leftMapLeft = $leftMapLeft")
}

Either<A, B> can be transformed to Either<B,A> using the swap() method.

import arrow.core.Either.Left
import arrow.core.Either

//sampleStart
val r: Either<String, Int> = Either.Right(7)
val swapped = r.swap()
//sampleEnd
fun main() {
println("swapped = $swapped")
}

For using Either's syntax on arbitrary data types. This will make possible to use the left(), right(), getOrElse() methods:

import arrow.core.right

val right7 =
//sampleStart
7.right()
//sampleEnd
fun main() {
println(right7)
}
import arrow.core.left

val leftHello =
//sampleStart
"hello".left()
//sampleEnd
fun main() {
println(leftHello)
}
import arrow.core.left
import arrow.core.getOrElse

//sampleStart
val x = "hello".left()
val value = x.getOrElse { "$it world!" }
//sampleEnd
fun main() {
println("value = $value")
}

Another operation is fold. This operation will extract the value from the Either, or provide a default if the value is Left

import arrow.core.Either
import arrow.core.right

//sampleStart
val x : Either<Int, Int> = 7.right()
val fold = x.fold({ 1 }, { it + 3 })
//sampleEnd
fun main() {
println("fold = $fold")
}
import arrow.core.Either
import arrow.core.left

//sampleStart
val y : Either<Int, Int> = 7.left()
val fold = y.fold({ 1 }, { it + 3 })
//sampleEnd
fun main() {
println("fold = $fold")
}

The getOrHandle() operation allows the transformation of an Either.Left value to a Either.Right using the value of Left. This can be useful when mapping to a single result type is required like fold(), but without the need to handle Either.Right case.

As an example, we want to map an Either<Throwable, Int> to a proper HTTP status code:

import arrow.core.Either
import arrow.core.getOrElse

//sampleStart
val r: Either<Throwable, Int> = Either.Left(NumberFormatException())
val httpStatusCode = r.getOrElse {
when(it) {
is NumberFormatException -> 400
else -> 500
}
}
//sampleEnd
fun main() {
println("httpStatusCode = $httpStatusCode")
}

Inheritors

Types

Link copied to clipboard
object Companion
Link copied to clipboard
data class Left<out A>(val value: A) : Either<A, Nothing>

The left side of the disjoint union, as opposed to the Right side.

Link copied to clipboard
data class Right<out B>(val value: B) : Either<Nothing, B>

The right side of the disjoint union, as opposed to the Left side.

Functions

Link copied to clipboard
Link copied to clipboard
fun <A, B> Either<A?, B?>.bisequenceNullable(): Either<A, B>?
Link copied to clipboard
Link copied to clipboard
inline fun <E, T : Throwable, A> Either<Throwable, A>.catch(catch: Raise<E>.(T) -> A): Either<E, A>

Variant of Either.catchOrThrow constructor that allows for working with Either<Throwable, A> by transforming or recovering from Throwable as T in the Either.Left side. This API is the same as recover. This is useful when working with results of Either.catch since this API offers a reified variant.

Link copied to clipboard
fun <A, B> Either<A, B>.combine(other: Either<A, B>, combineLeft: (A, A) -> A, combineRight: (B, B) -> B): Either<A, B>

Combine two Either values. If both are Right then combine both B values using combineRight or if both are Left then combine both A values using combineLeft, otherwise it returns the this or fallbacks to other in case this is Left.

Link copied to clipboard
operator fun <A : Comparable<A>, B : Comparable<B>> Either<A, B>.compareTo(other: Either<A, B>): Int
Link copied to clipboard
inline fun <A, B, C> Either<A, B>.flatMap(f: (right: B) -> Either<A, C>): Either<A, C>

Map, or transform, the right value B of this Either into a new Either with a right value of type C. Returns a new Either with either the original left value of type A or the newly transformed right value of type C.

Link copied to clipboard
fun <A, B> Either<A, Either<A, B>>.flatten(): Either<A, B>
Link copied to clipboard
inline fun <C> fold(ifLeft: (left: A) -> C, ifRight: (right: B) -> C): C

Transform an Either into a value of C. Alternative to using when to fold an Either into a value C.

Link copied to clipboard
infix inline fun <A, B> Either<A, B>.getOrElse(default: (A) -> B): B

Get the right value B of this Either, or compute a default value with the left value A.

Link copied to clipboard

Transforms Either into Option, where the encapsulated value B is wrapped in Some when this instance represents Either.Right, or None if it is Either.Left.

Link copied to clipboard
fun getOrNull(): B?

Returns the unwrapped value B of Either.Right or null if it is Either.Left.

Link copied to clipboard

inline fun isLeft(predicate: (A) -> Boolean): Boolean

Returns false if Right or returns the result of the given predicate to the Left value.

Link copied to clipboard

inline fun isRight(predicate: (B) -> Boolean): Boolean

Returns false if Left or returns the result of the given predicate to the Right value.

Link copied to clipboard
fun leftOrNull(): A?

Returns the unwrapped value A of Either.Left or null if it is Either.Right.

Link copied to clipboard
inline fun <C> map(f: (right: B) -> C): Either<A, C>

Map, or transform, the right value B of this Either to a new value C.

Link copied to clipboard
inline fun <C> mapLeft(f: (A) -> C): Either<C, B>

Map, or transform, the left value A of this Either to a new value C.

Link copied to clipboard
inline fun <A> Either<A, A>.merge(): A

Returns the value from this Right or Left.

Link copied to clipboard
inline fun onLeft(action: (left: A) -> Unit): Either<A, B>

Performs the given action on the encapsulated A if this instance represents Either.Left. Returns the original Either unchanged.

Link copied to clipboard
inline fun onRight(action: (right: B) -> Unit): Either<A, B>

Performs the given action on the encapsulated B value if this instance represents Either.Right. Returns the original Either unchanged.

Link copied to clipboard
inline fun <E, EE, A> Either<E, A>.recover(recover: Raise<EE>.(E) -> A): Either<EE, A>

Recover from any Either.Left if encountered.

Link copied to clipboard
fun swap(): Either<B, A>

Swap the generic parameters A and B of this Either.

Link copied to clipboard
fun <E, A> Either<E, A>.toEitherNel(): EitherNel<E, A>
Link copied to clipboard
fun toIor(): Ior<A, B>
Link copied to clipboard
open override fun toString(): String