Kotlin “Result” type for functional exception handling
In a previous post I had gone over how a “Try” type can be created in Kotlin from scratch to handle exceptions in a functional way. There is no need however to create such a type in Kotlin, a type called “Result” already handles the behavior of “Try” and this post will go over how it works. I will be taking the scenario from my previous post, having two steps:
- Parsing a Url
- Fetching from the Url
Either of these steps can fail
- the URL may not be well formed, and
- fetching from a remote url may have network issues
So onto the basics of how such a call can be made using the Result type. You can imagine that parsing URL can return this Result type, capturing any exception that may result from such a call:
1 2 | fun parseUrl(url: String): Result<URL> = kotlin.runCatching { URL(url) } |
Kotlin provides the “runCatching” function which accepts the block that can result in an exception and traps the result OR the exception in the “Result” type. Now that a “Result” is available, some basic checks can be made on it, I can check that the call succeeded using the “isSuccess” and “isFailure” properties:
1 2 3 | urlResult.isSuccess == true urlResult.isFailure == false |
I can get the value using various “get*” methods:
1 2 3 | urlResult.getOrNull() // Returns null if the block completed with an exception urlResult.getOrDefault(URL( "http://somedefault" )) // Returns a default if the block completed with an exception urlResult.getOrThrow() // Throws an exception if the block completed with an exception |
The true power of “Result” type is however in chaining operations on it. So for eg, if you wanted to retrieve the host name given the url:
1 2 | val hostResult: Result<String> = urlResult.map { url -> url.host } |
Or a variant “mapCatching” which can trap any exception when using map operation and capture that as a “Result”:
1 | val getResult: Result<String> = urlResult.mapCatching { url -> throw RuntimeException( "something failed!" ) } |
All very neat! One nit that I have with the current “Result” is a missing “flatMap” operation, so for eg. consider a case where I have these two functions:
1 2 3 4 5 6 | fun parseUrl(url: String): Result<URL> = kotlin.runCatching { URL(url) } fun getFromARemoteUrl(url: URL): Result<String> { return kotlin.runCatching { "a result" } } |
I would have liked to be able to chain these two operations, along these lines:
1 2 | val getResult: Result<String> = urlResult.flatMap { url -> getFromARemoteUrl(url)} |
but a operator like “flatMap” does not exist (so far, as of Kotlin 1.5.20)
I can do today is a bit of hack:
1 2 | val getResult: Result<String> = urlResult.mapCatching { url -> getFromARemoteUrl(url).getOrThrow() } |
OR even better, create an extension function which makes flatMap available to “Result” type, this way and use it:
1 2 3 4 5 6 7 | fun <T, R> Result<T>.flatMap(block: (T) -> (Result<R>)): Result<R> { return this .mapCatching { block(it).getOrThrow() } } val getResult: Result<String> = urlResult.flatMap { url -> getFromARemoteUrl(url)} |
This concludes my exploration of the Result type and the ways to use it. I have found it to be a excellent type to have in my toolbelt.
Published on Java Code Geeks with permission by Biju Kunjummen, partner at our JCG program. See the original article here: Kotlin “Result” type for functional exception handling Opinions expressed by Java Code Geeks contributors are their own. |