Which way do we usually design a method that in some circumstances is expected to fail?
- Throw an exception from the method body. And warn caller to catch it on a calling side.
fun devide(num: Int, denom: Int): Int {
return if (denom != 0) num / denom
else throw ArithmeticException("division by zero") // let's pretend we should care about the division by zero manually, just for demo purposes
}
val value: Int = try {
devide(1, 0)
} catch (x: Exception) {
println("Oops: $x")
-1
}
- Or return
null
orOptional
if we don’t want to bother a caller with an exception handling. However, the caller misses info about what’s gone wrong.
fun devide(num: Int, denom: Int): Int? {
return if (denom != 0) num / denom
else null
}
val value: Int = devide(1, 0) ?: run {
println("Oops: something's gone wrong")
-1
}
For this purpose, the Scala’s Try
monad was created. A returned result can be either a Success
that wraps the requested value or a Failure
that wraps an exception. This approach facilitates an alternative, more convenient and intelligible code style. But as for now, there’s nothing similar in Kotlin.
Yet recently I’ve come across the nice and simple solution, proposed by Roman @elizarov here with the feature request here.
fun devide(num: Int, denom: Int): Result<Int> {
return if (denom != 0) Result.Success(num / denom)
else Result.Failure(ArithmeticException("division by zero"))
// or even simpler: return resultOf{ num / denom }
}
val value: Int = devide(1, 0).onFailure{println("Oops: $it")}.getOrElse{ -1 }
To my mind, it should be as nice, simple and easy as it is.
There are alternative proposals to make it monadic i.e. the way the Scala Try
or Either
is. However, one essential advantage of the Result
implementation above, over monadic one is that the former is optimized for a successful result. Whereas the monadic implementation is symmetric and a bit heavy. And it’s important because when I count precious nanoseconds I’d prefer “throwing and handling” style over “wrapping result” style if it takes longer than a cost of a simple method call. And for now, the Result
implementation is equal or even faster than Java’s Optional
.
What are your considerations?