While in programming you are bound to catch errors. One of the worst implementations I've seen is `Go`

f, err := os.Open("filename.ext")
if err != nil {
    log.Fatal(err)
}
// do something with the open *File f

An if statement after each process to check an error occurrence. Java was bounded by try catch.

try {
  // Do Something
} catch (e:Exception) {
  // Handle Error
}

The surrounding impetus of this problem. Is that errors are bound to occur. This is not even including futures or other forms of concurrency.

With Kotlin we are in a unique position. Where we can expose functional data types from the back end to the front end. But what are these functional data types.

The key ones I feel to use is.

  • Try
  • Either
  • Option

Planning your Data Types

Before delving into each respective data type. It is important to talk composition/design of your application. In any given application you may be talking to a database, another service, etc. Each of those items can have both a different result type, and error type.

If we were to corral the error type into a common interface. Having the application avoid stack traces. Returning an error that we can iterate over. This makes comprehension easier. You no longer have to worry about handling a myriad of errors. It's one common explanation of errors.

val possibleDBResponse : Either<Error, ?> = makeDBCall()
val possibleRestResponse : Either<Error, ?> = makeRestCall()
val possibleGRPCCall : Either<Error, ?> = makeGRPCCall()

In the above snippet. Irrespective of the response type. We can see they all return a sub class of Error. This will become more apparent as to it's benefit in the following sections. But designing with one common response enclosure from top to bottom will ease implementation.

Either

This is my most common used data type. A rough definition is as follows.

sealed class Either<out E, out R> {
    data class Left<E : Any, R : Any>(val left: E) : Either<E, R>()
    data class Right<E : Any, R : Any>(val right: R) : Either<E, R>()
}

We are saying that an operation may have either a Left or Right response. They are generic containers. So the type is interchangeable.

There is a term with these and that is bias. A bias denotes the happy path forward. Meaning that the left container holds an error, or incorrect pattern.

There are a number of ways to handle an Either.

val possibleGists : Either<Error, List<Gists>> = getGists(user)
when (possibleGists) {
   is Left -> throw exception("Error")
   is Right -> Happy Path
}
if (Either is Right) {
  // Happy Path
} else {
  // Sad Path
}

But looking at the above this isn't much better than the if or try/catch method. We still have to individually handle each individual error. If we were to have several attempts in a row.

val possibleGists : Either<Error, List<Gists>> = getGists(user)
val possibleRecentCommits : Either<Error, List<Commit>> = getCommits(user)
val possibleRepositories : Either<Error, List<Repository>> = getRepositories(user)
// Magic
println("$user has written ${gists.size}, owns ${repositories.size}, and has ${commits.size} recent commits")

If we have each of those items. Which can return an either or an error. It would be very cumbersome to manually check each items. The last statement is expecting the values to be the actual response type i.e. List<Gists>>.

I've heard this called in two common ways. Neither of which seemed to be recognized by the scala developers I've worked with. Monadic comprehensions or railway programming. Monad is a scary word, so let's break this down.

A DSL has been built to handle the Either data type. Using the destructuring feature of Kotlin. We attempt to force the Right value from the data type. If it is a Left value. Where an error has occurred, then we break out of the comprehension. Returning the most recent error.

val rsp :Either<Error, String> = Either.comprehension {
  val (gists : List<Gists>) = getGists(user)
  val (recentCommits: List<Commit>) = getCommits(user)
  val (repositories) = getRepositories(user)
  "$user has written ${gists.size}, owns ${repositories.size}, and has ${commits.size} recent commits"
}
when (rsp) {
  is Left -> println("An error occurred: ${rsp.left.message}")
  is Right -> println(rsp.right)
}

In the following sample we execute all the above calls. But we assume a happy path. Returning the template string. After the comprehension we only have to handle one error.

This is why I opened with focusing on a cohesive common data flow pattern through your application. By having a common Error type we can do these comprehensions.  

Behind the Scenes

The Comprehension builds an instance of a EitherComprehension class. This over rides the operator component1 function. Called when encapsulating a variable in ().  Below is the snippet.

    operator fun <E : Any, R : Any> Either<E, R>.component1() = when (this) {
        is Either.Left -> {
            errorOccurred = true
            error = this.left
            throw Exception()
        }
        is Either.Right -> this.right
    }

While throwing an exception is not the most optimal. It was the cleanest way to stop execution of the closure. If any Left is encountered, it will set an error, and stop the closure.

        fun <E : Any, R : Any> comprehension(block: EitherComprehension<E, R>.() -> R): Either<E, R> {
            val state = EitherComprehension<E, R>()
            return try {
                Either.right(state.block())
            } catch (e: Exception) {
                val error = state.error as E
                left(error)
            }
        }

The exception is inconsequential to the output / error. We catch and discard the exception. Pulling the error from the EitherComprehension class. Placing it in an Either.left instance.

With this we have a clean return type, and can define a clear progression of our data flow. Without having to worry about handling each possible error.

Either Concurrency

This is all well and dandy. But what about co-routines?

val futureGists : Deferred<Either<Error, List<Gists>> = getGistsAsync(user)
when (val possibleGists = futureGists.await()) {
  is Left -> //error
  is Right -> //happy
}

That again seems cumbersome. What if we could do a comprehension.

val rsp :Either<Error, String> = Either.comprehension {
  val futureGists : Deferred<Either<Error, List<Gists>> = getGistsAsync(user)
  val futureCommits : Deferred<Either<Error, List<Commit>> = getCommits(user)
  val futureRepositories = getRepositoriesAsync(user)
  val (gists) = !futureGists // futureGists.await()
  val (commits) = !futureCommits // futureCommits.await()
  val (repositories) = !futureRepositories
  "$user has written ${gists.size}, owns ${repositories.size}, and has ${commits.size} recent commits"
}
when (rsp) {
  is Left -> println("An error occurred: ${rsp.left.message}")
  is Right -> println(rsp.right)
}

This is remarkebly similar to the prior example. We just have double the definitions. The first launches the async operation. The second waits on the response. We have co-opted an F# operator !. That simply awaits on the response, without having to type out await. After that it gets unpacked like before.

What if you don't want to wait later, or use await or !?

val rsp :Either<Error, String> = Either.comprehension {
  val (gists) = getGistsAsync(user)
  val (commits) = getCommits(user)
  val (repositories) = getRepositoriesAsync(user)
  "$user has written ${gists.size}, owns ${repositories.size}, and has ${commits.size} recent commits"
}
when (rsp) {
  is Left -> println("An error occurred: ${rsp.left.message}")
  is Right -> println(rsp.right)
}

This is less raw lines of code. But will hinder performance. You're essentially blocking after each call. The component1 call immediately awaits on each deferred operation.

For now the first approach while slightly wordy. Is the best approach.

Option

An option say you May have a result. It is ether a result of Some or None. When it is None we are not containing an error, or saying what happened. It's just nothing. Some will contain a generic type.

sealed class Option<out S:Any> {
    class None<I:Any>() : Option<I>()
    data class Some<I:Any>(val item:I) :Option<I>()
}

Much like either there are a number of helper methods surrounding the option type. For simplicity I will only show the comprehension.

        val a = Option.Some(1)
        val b = Option.Some(2)
        val rsp = Option.comprehension<Int> {
            val (possibleA) = a
            val (possibleB) = b
            possibleA + possibleB
        }

If any part of this chain has a None. The comprehension will stop. We are assuming that everything in the chain must return a proper type of Some.

Closing

In closing. These functional data types allow us to ease error handling. Describing a chain of events, and with a common error type. We can then respond with where in the chain it failed.