DEV Community

Cover image for Intro to Eithers in Android
Eric Donovan
Eric Donovan

Posted on • Updated on

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!

Top comments (15)

Collapse
 
viaanamichale profile image
Vishangi M • Edited

Thanks for introducing us on Eithers. This will definitely helping many of us. I’ll definitely sharing it with my colleagues. Keep writing and sharing more posts with us.
Regards, Viaana.
Sr. DGM at Android App Development Company in USA.

Collapse
 
hakanai profile image
Hakanai • Edited

What you have invented here is typically called Try<T>, sometimes seen as Maybe<T>. It contains the result of an operation or an exception.

I should add, it's a nice pattern for cases where you want the caller to think about the possibility of an exception. Where it starts to get messy is when there are multiple possible reasons for the exception.

Collapse
 
erdo profile image
Eric Donovan

hmm, it's not really something I invented, the first version (with the Left and Right) is directly taken from the Arrow functional library. I wonder if the name Try has been avoided in kotlin (java) because of it's keyword status.

The Try<> you mention only has one generic, which I think doesn't quite map to these Eithers

Collapse
 
hakanai profile image
Hakanai • Edited

You can add a second generic parameter for the exception type if there's any reason you would want to cast down to the exception type, works fine. Just in the majority of cases Throwable is adequate so the type parameter gets omitted.

Editing to say there are actually two ways to go about it.

  1. You say all exceptions are just the same thing, Throwable, in which case you omit the parameter. Then when someone gets one, they inspect what they got, and use when or whatever.
  2. You generate a family of Try<T, X1>, Try<T, X1, X2>, Try<T, X1, X2, X3> classes to capture more and more separate errors.
Thread Thread
 
erdo profile image
Eric Donovan

I think you're describing a different thing entirely. Eithers typically don't contain exceptions as one of the values, they typically contain an error. You can use Eithers in kotlin as a way to avoid using exceptions in your API (whereas in Java it might be more idiomatic to handle exceptions). There's a bit more on that difference here: elizarov.medium.com/kotlin-and-exc...

Thread Thread
 
hakanai profile image
Hakanai

Error and exception are just two words for the same thing. Sure, in Java "Error" was reserved to mean specific kinds of error which were more fatal while "Exception" was for more recoverable ones, other languages reversed this distinction or didn't have it at all, so we can basically treat them as synonyms.

Which way you then implement them is up to what's possible in the language. In Java we had the luxury of being able to throw them but in some other languages, returning different values is really the only way. In the more procedural languages, people either tended to return the error code, or return a sentinel result. In functional languages, the Try monad and similar structures were the common way to go about this.

Using such structures does not avoid using exceptions - it's another way to implement exceptions.

Thread Thread
 
erdo profile image
Eric Donovan

This is getting a bit trolly don't you think? Your original comment was about Try<> but this article is about Either<>.

Now you're explaining to me that Errors and Exceptions are the same thing - in the context of this article, and in the article posted above, they do not mean the same thing at all. Exception is being used to mean part of Java's exception handling - the things that you try / catch. Error is being used to mean a regular class like an enum or a sealed class which forms part of a standard type safe API.

As I mention above "You can use Eithers in kotlin as a way to avoid using exceptions in your API".

I think you should write an article about Try<> it might be interesting

Thread Thread
 
hakanai profile image
Hakanai • Edited

Fair enough, you're just artificially separating the concepts.

And no, telling people how things are is not "trolling". And calling something something different doesn't really change its nature either. An error is an error, whether it's modelled by throwing, raising, returning special values, monads, or anything.

And like I already pointed out, you're not avoiding using exceptions, you're just implementing them in a different way. The exceptional condition is still there. You're not avoiding them "in your API" because you're not avoiding them at all. You're just implementing them differently. Is this different way better? Quite possibly. Especially in a language like Kotlin where you can't force people to handle the thrown ones.

I would recommend learning more than a couple of programming languages and seeing what else is out there. You'll see the common patterns between them as you pick more up.

Also, the normal use case for Either, for what it's worth, is not for error conditions, but for situations where there are two possible outcomes. Saying that an error is a second possible outcome... well, I guess that's sort of true, but usually you'd call that Try. The person reading the code is going to understand when they see Try that the alternative outcome is an error. If they see Either, they wouldn't know.

Thread Thread
 
erdo profile image
Eric Donovan

Go back and read your first comment:

What you have invented here is typically called Try, sometimes seen as Maybe. It contains the result of an operation or an exception.

You apparently didn't know Eithers were a thing, and you sought to teach me in public that I had mistakenly re-invented a Try, named it Either, and then written a whole article about it.

I've tried to remain polite, but honestly your comments would be much more welcome on Reddit. Dev.to has a great community, and this has been a very disappointing interaction. Please get off my page

Thread Thread
 
hakanai profile image
Hakanai • Edited

Nope, I knew Eithers were a thing as well. They're just not generally for putting an exception case as the second alternative. For that, there is Try.

And hey, I was being polite until you called what I was saying trolling. If you throw a stone, you better expect someone is going to pick it up and throw it back at you.

Collapse
 
leosantana profile image
Leandro Santana

Very nice article! I've been using this approach on my projects. Sometimes when the type of failure doesn't matter I rename Either to another word like Response and change the Left (or F) generic type to Exception or Throwable or Any depending on the situation.

CarryOn extension is also a very good idea to perform chain operations.

Collapse
 
b_plab98 profile image
Biplab Dutta 🇳🇵📱🧑‍💻

Yes. I come from Flutter background and have been using Either to not allow my exceptions from data layer to reach the presentation layer. Either, Option I think everyone should try it and when they do, they'd fall in love with it.

Link to my blog on Either in Flutter

Collapse
 
erdo profile image
Eric Donovan

Nice! I've never used Flutter but it looks quite well designed 👍

Collapse
 
kururu95 profile image
kururu

need to install third party library?

Collapse
 
erdo profile image
Eric Donovan

Oh no, just add this to your code somewhere:


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

The Arrow library gives you lots of functional goodies but it's pretty large. The fore library is a lot smaller and you'll get that exact Either above - but still probably not worth it if it's just for the Either, just copy paste it