Try is a representation of a computation that may or may not fail. Try<A> is a sealed class with two possibles sub-classes—Failure<A>, representing a fail and Success<T> representing a successful operation.
Let's write our division example with Try:
import arrow.data.Try
fun tryDivide(num: Int, den: Int): Try<Int> = Try { divide(num, den)!! }
The easiest way to create a Try instance is to use the Try.invoke operator. If the block inside throws an exception, it will return Failure; if everything goes well, Success<Int>, for example, the !! operator will throw NPE if divide returns a null:
fun tryDivision(a: Int, b: Int, den: Int): Try<Tuple2<Int, Int>> {
val aDiv = tryDivide(a, den)
return when (aDiv) {
is Success -> {
val bDiv = tryDivide(b, den)
when (bDiv) {
is Success -> {
Try { aDiv.value toT bDiv.value }
}
is Failure -> Failure(bDiv.exception)
}
}
is Failure -> Failure(aDiv.exception)
}
}
Let's take a look at a short list of the Try<T> functions:
Function |
Description |
exists(p: Predicate<T>): Boolean |
If Success<T> returns p result, on Failure always return false. |
filter(p: Predicate<T>): Try<T> |
Returns Success<T> if operation is successful and pass predicate p, otherwise Failure. |
<R> flatMap(f: (T) -> Try<R>): Try<R> |
flatMap function as in monad. |
<R> fold(fa: (Throwable) -> R, fb:(T) -> R): R |
Returns value transformed as R, invoking fa if Failure. |
getOrDefault(default: () -> T): T |
Returns value T, invoking default if Failure. |
getOrElse(default: (Throwable) -> T): T |
Returns value T, invoking default if Failure. |
isFailure(): Boolean |
Returns true if Failure, otherwise false. |
isSuccess(): Boolean |
Returns true if Success, otherwise false. |
<R> map(f: (T) -> R): Try<R> |
Transforming function as in functor. |
onFailure(f: (Throwable) -> Unit): Try<T> |
Act on Failure. |
onSuccess(f: (T) -> Unit): Try<T> |
Act on Success. |
orElse(f: () -> Try<T>): Try<T> |
Returns itself on Success or f result on Failure. |
recover(f: (Throwable) -> T): Try<T> |
Transform map function for Failure. |
recoverWith(f: (Throwable) -> Try<T>): Try<T> |
Transform flatMap function for Failure. |
toEither() : Either<Throwable, T> |
Transform into Either—Failure to Left<Throwable> and Success<T> to Right<T>. |
toOption(): Option<T> |
Transform into Option—Failure to None and Success<T> to Some<T>. |
The flatMap implementation is very similar to Either and Option and shows the value of having a common set of name and behavior conventions:
fun flatMapTryDivision(a: Int, b: Int, den: Int): Try<Tuple2<Int, Int>> {
return tryDivide(a, den).flatMap { aDiv ->
tryDivide(b, den).flatMap { bDiv ->
Try { aDiv toT bDiv }
}
}
}
Monadic comprehensions are available for Try too:
fun comprehensionTryDivision(a: Int, b: Int, den: Int): Try<Tuple2<Int, Int>> {
return Try.monad().binding {
val aDiv = tryDivide(a, den).bind()
val bDiv = tryDivide(b, den).bind()
aDiv toT bDiv
}.ev()
}
There is another kind of monadic comprehension using an instance of MonadError:
fun monadErrorTryDivision(a: Int, b: Int, den: Int): Try<Tuple2<Int, Int>> {
return Try.monadError().bindingCatch {
val aDiv = divide(a, den)!!
val bDiv = divide(b, den)!!
aDiv toT bDiv
}.ev()
}
With monadError.bindingCatch any operation that throws an exception is lifted to Failure, at the end the returns is wrapped into Try<T>. MonadError is also available for Option and Either.