Intro to Eithers in Android

erdo

Eric Donovan

Posted on June 9, 2022

Intro to Eithers in Android

It's not often you come across Eithers in android code, which I think is a shame given how awesome they are. (We've been using Eithers to chain networking results and handle errors in fore since late 2019).

The thing is, they are a little... weird.

I think we all instinctively understand what an Either is: it's a single type that represents either thing A or thing B; never both; never neither.

Nothing in that description talks about errors or successes, so, many implementations use the more generically applicable names: Left and Right. And by convention (if we are dealing with success / error data) Left is used for the error case, and Right is used for the success case (it's the "right" answer after all).

Either.left(ERROR_NETWORK)
Enter fullscreen mode Exit fullscreen mode

bit weird.

One of the most famous implementations of Either is from the Arrow-Core library. For a while, they were using the names "a" and "b" (they use "value" now) to represent the data contained by the Left and Right either respectively, so we had this:

when(either){
    is Either.Left -> either.a // bad stuff
    is Either.Right -> either.b // good stuff
}
Enter fullscreen mode Exit fullscreen mode

still weird.

Don't get me wrong, if Eithers are your bread and butter, this is normal stuff that fades into the background. Why would anyone put the success on the Left anyway???

Despite that though, I've found that Either's very slight weirdness (and the requirement that you need to remember that Left holds the error case) is enough to put non-functional developers off using them.

Which is a shame.


A less weird Either

There is nothing special about the words Left and Right, those words are only generic terms if you happen to not be developing a driving app for instance (where the words Left and Right would have a special significance). In fore (and probably in most cases) the Eithers are exclusively dealing with success / error data. And that's why fore changed its Either implementation to this instead (feel free to copy paste into your own project):

sealed class Either<out F, out S> {

  data class Fail<out F> internal constructor(val value: F) : Either<F, Nothing>() {
    companion object {
      operator fun <F> invoke(f: F): Either<F, Nothing> = Fail(f)
    }
  }

  data class Success<out S> internal constructor(val value: S) : Either<Nothing, S>() {
    companion object {
      operator fun <S> invoke(s: S): Either<Nothing, S> = Success(s)
    }
  }

  companion object {
    fun <S> success(value: S): Either<Nothing, S> = Success(value)
    fun <F> fail(value: F): Either<F, Nothing> = Fail(value)
  }
}
Enter fullscreen mode Exit fullscreen mode

So now we can make them like this

success(serviceResponse)

/** or **/

fail(ERROR_NETWORK)
Enter fullscreen mode Exit fullscreen mode

and use them like this

when(either){
    is Fail -> either.value // sad face
    is Success -> either.value // happy face
}
Enter fullscreen mode Exit fullscreen mode

or with auto-complete

coding autocomplete gif, pressing alt enter from a when will give you the option to automatically complete the Success and Fail branches
less weird.

Fail or Error

Why did we use Fail instead of Error? Because every time you type Error in your IDE when writing kotlin, you will immediately be referencing:

public actual typealias Error = java.lang.Error
Enter fullscreen mode Exit fullscreen mode

Which is not what you want. Fail doesn't have this problem.

Converting Eithers

Just in case you wanted some other kind of Either (like the one from Arrow) it's very easy to convert between eithers:

fun <F, S> Either<F, S>.toArrow(): arrow.core.Either<F, S> {
    return when(this){
        is Fail ->  arrow.core.Either.left(this.value)
        is Success -> arrow.core.Either.right(this.value)
    }
}

/** which can be used like this **/

val arrowEither = foreEither.toArrow()

Enter fullscreen mode Exit fullscreen mode

So you said Eithers were good?

That's probably a whole article in itself. But as long as you set them up properly, they can help you handle error conditions in such a way that the compiler will notice if you don't do it. That's why fore uses them in the networking CallWrapper classes.

The reason I absolutely love them though, is that they are so easy to chain together. With this little extension function we get some pretty amazing wins:

suspend fun <E, S, S2> Either<E, S>.carryOn(
        nextBlock: suspend (S) -> Either<E, S2>
): Either<E, S2> {
    return when (this) {
        is Either.Fail -> this
        is Either.Success -> nextBlock(value)
    }
}
Enter fullscreen mode Exit fullscreen mode

What that does is to call the nextBlock() whenever the either is a Success (thus continuing the flow of code) or just return the either if it was a Fail. If you were pedantic enough, you might call that function carryOnIfThatLastOperationWasASuccess(). It lets us write things like this:

val response = createUserUseCase()
  .carryOn { user ->
    createUserTicketUseCase(user.userId)
  }.carryOn { ticket ->
    ticketRef = ticket.ticketRef
    getEstimatedWaitingTimeUseCase(it.ticketRef)
  }.carryOn { minutesWait ->
    if (minutesWait > 10) {
        cancelTicketUseCase(ticketRef)
    } else {
        confirmTicketUseCase(ticketRef)
    }
  }.carryOn {
    claimFreeGiftUseCase(ticketRef)
  }

when (response) {
    is Fail -> handleFailure(response.value)
    is Success -> handleSuccess(response.value)
}
Enter fullscreen mode Exit fullscreen mode

If all those useCases return Eithers, we basically have:

  • no callback hell
  • highly dense business logic
  • complete confidence that errors are being handled

You can see this technique being used to create a weather report by calling separate wind speed, pollen level, and temperature services in this clean modules sample app


I'd highly encourage you to give Eithers a go if you haven't already, you might be pleasantly surprised! Check the Arrow link at the top if you want to dig deeper. Also check out fore :) it's meant to make things like this very easy and concise. Thanks for reading!

💖 💪 🙅 🚩
erdo
Eric Donovan

Posted on June 9, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Intro to Eithers in Android
android Intro to Eithers in Android

June 9, 2022