

all and sundry: Kotlin - Try type for functional exception handling
source link: http://www.java-allandsundry.com/2017/12/kotlin-try-type-for-functional.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Kotlin - Try type for functional exception handling
Scala has a Try type to functionally handle exceptions. I could get my head around using this type using the excellent Neophyte's guide to Scala by Daniel Westheide. This post will replicate this type using Kotlin.
Background
Consider a simple function which takes two String, converts them to integer and then divides them(sample based on scaladoc of Try) :
fun divide(dividend: String, divisor: String): Int { val num = dividend.toInt() val denom = divisor.toInt() return num / denom } |
It is the callers responsibility to ensure that any exception that is propagated from this implementation is handled appropriately using the exception handling mechanism of Java/Kotlin:
try { divide( "5t" , "4" ) } catch (e: ArithmeticException) { println( "Got an exception $e" ) } catch (e: NumberFormatException) { println( "Got an exception $e" ) } |
My objective with the "Try" code will be to transform the "divide" to something which looks like this:
fun divideFn(dividend: String, divisor: String): Try<Int> { val num = Try { dividend.toInt() } val denom = Try { divisor.toInt() } return num.flatMap { n -> denom.map { d -> n / d } } } |
A caller of this variant of "divide" function will not have an exception to handle through a try/catch block, instead, it will get back the exception as a value which it can introspect and act on as needed.
val result = divideFn( "5t" , "4" ) when(result) { is Success -> println( "Got ${result.value}" ) is Failure -> println( "An error : ${result.e}" ) } |
Kotlin implementation
The "Try" type has two implementations corresponding to the "Success" path or a "Failure" path and implemented as a sealed class the following way:
sealed class Try<out T> {} data class Success<out T>(val value: T) : Try<T>() {} data class Failure<out T>(val e: Throwable) : Try<T>() {} |
The "Success" type wraps around the successful result of an execution and "Failure" type wraps any exception thrown from the execution.
So now, to add some meat to these, my first test is to return one of these types based on a clean and exceptional implementation, along these lines:
val trySuccessResult: Try<Int> = Try { 4 / 2 } assertThat(trySuccessResult.isSuccess()).isTrue() val tryFailureResult: Try<Int> = Try { 1 / 0 } assertThat(tryFailureResult.isFailure()).isTrue() |
This can be achieved through a "companion object" in Kotlin, similar to static methods in Java, it returns either a Success type or a Failure type based on the execution of the lambda expression:
sealed class Try<out T> { ... companion object { operator fun <T> invoke(body: () -> T): Try<T> { return try { Success(body()) } catch (e: Exception) { Failure(e) } } } ... } |
Now that a caller has a "Try" type, they can check whether it is a "Success" type or a "Failure" type using the "when" expression like before, or using "isSuccess" and "isFailure" methods which are delegated to the sub-types like this:
sealed class Try<out T> { abstract fun isSuccess(): Boolean abstract fun isFailure(): Boolean } data class Success<out T>(val value: T) : Try<T>() { override fun isSuccess(): Boolean = true override fun isFailure(): Boolean = false } data class Failure<out T>(val e: Throwable) : Try<T>() { override fun isSuccess(): Boolean = false override fun isFailure(): Boolean = true } |
in case of Failure a default can be returned to the caller, something like this in a test:
val t1 = Try { 1 } assertThat(t1.getOrElse( 100 )).isEqualTo( 1 ) val t2 = Try { "something" } .map { it.toInt() } .getOrElse( 100 ) assertThat(t2).isEqualTo( 100 ) |
again implemented by delegating to the subtypes:
sealed class Try<out T> { abstract fun get(): T abstract fun getOrElse( default : @UnsafeVariance T): T abstract fun orElse( default : Try< @UnsafeVariance T>): Try<T> } data class Success<out T>(val value: T) : Try<T>() { override fun getOrElse( default : @UnsafeVariance T): T = value override fun get() = value override fun orElse( default : Try< @UnsafeVariance T>): Try<T> = this } data class Failure<out T>(val e: Throwable) : Try<T>() { override fun getOrElse( default : @UnsafeVariance T): T = default override fun get(): T = throw e override fun orElse( default : Try< @UnsafeVariance T>): Try<T> = default } |
The biggest advantage of returning a "Try" type, however, is in chaining further operations on the type.
Chaining with map and flatMap
"map" operation is passed a lambda expression to transform the value in some form - possibly even to a different type:
val t1 = Try { 2 } val t2 = t1.map({ it * 2 }).map { it.toString()} assertThat(t2).isEqualTo(Success( "4" )) |
Here a number is being doubled and then converted to a string. If the initial Try were a "Failure" then the final value will simply return the "Failure" along the lines of this test:
val t1 = Try { 2 / 0 } val t2 = t1.map({ it * 2 }).map { it * it } assertThat(t2).isEqualTo(Failure<Int>((t2 as Failure).e)) |
Implementing "map" is fairly straightforward:
sealed class Try<out T> { fun <U> map(f: (T) -> U): Try<U> { return when ( this ) { is Success -> Try { f( this .value) } is Failure -> this as Failure<U> } } } |
flatmap, on the other hand, takes in a lambda expression which returns another "Try" type and flattens the result back into a "Try" type, along the lines of this test:
val t1 = Try { 2 } val t2 = t1 .flatMap { i -> Try { i * 2 } } .flatMap { i -> Try { i.toString() } } assertThat(t2).isEqualTo(Success( "4" )) |
Implementing this is simple too, along the following lines:
sealed class Try<out T> { fun <U> flatMap(f: (T) -> Try<U>): Try<U> { return when ( this ) { is Success -> f( this .value) is Failure -> this as Failure<U> } } } |
The "map" and "flatMap" methods are the power tools of this type, allowing chaining of complex operations together focusing on the happy path.
Conclusion
Try is a powerful type allowing a functional handling of exceptions in the code. I have a strawman implementation using Kotlin available in my github repo here - https://github.com/bijukunjummen/kfun
Recommend
-
141
Handling Kotlin Exceptions with Kategory – A Functional Approach by Roberto Guerra on Sep 15, 2017 Kotlin has excited us at Spantree...
-
66
A blog with observations on Java and Enterprise Java based technologies
-
55
-
139
This is a continuation of our series on exception handling and validation in Spring Boot REST APIs. Inearlier posts, we discussed a robust pattern for exception handling and validation in Spring Boot and MVC REST Web Ser...
-
51
Join GitHub today GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
-
9
Kotlin Coroutines Exception Handling Cheat Sheet
-
9
Generating a stream of Fibonacci numbers A Java stream represents potentially an infinite sequence of data. This is a simple post that will go into the mechanics involved in generating a simple stream of
-
8
Exception handling in Kotlin CoroutinesThis is a chapter from the book
-
8
美国服装零售商DBG收购女装品牌Sundry-跨境头条-AMZ123亚马逊导航-跨境电商出海门户 美国服装零售商DBG收购女装品牌Sundry...
-
8
Functional Error Handling in Kotlin, Part 1: Absent values, Nullables, Options 22 minute read
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK