The Either Type

In the previous article, we discussed functional error handling using Try, I also mentioned the existence of another similar type called Either.

From VAVR docs:

Either represents a value of two possible types. An Either is either a Left or a Right.

What on the green earth is that?

Like Try, Either is a container type. Unlike the aforementioned one, it takes not only one, but two type parameters: an Either<A, B> instance can contain either an instance of A, or an instance of B. This is different from a Tuple, which contains both an A and a B instance.

By convention, a Left value signifies a failure case result and a Right value signifies a success. You can think of a (say) Try<String> as an Either<Throwable, String>.

How to create an Either

// Remember this?
Alcoholic buyAlcoholic(Customer customer) throws UnderageException {
    if (customer.getAge() < 18) {
        throw new UnderageException("Age: " + customer.getAge());
    }
    return new Alcoholic(5);
}
// From boring to fun!
public class UnderageError {
    private final String reason;
    public String getReason() {
        return reason;
    }
}
Either<UnderageError, Alcoholic> buyAlcoholicEither(Customer customer) {
    if (customer.getAge() < 18) {
        return Either.left(new UnderageError("Age: " + customer.getAge()));
    }
    return Either.right(new Alcoholic(5));
}

If we call buyAlcoholicEither(new Customer(16)) we will get an UnderageError wrapped in a Left. If we pass in new Customer(20), the return value will be a Right containing a Alcoholic.

How to use an Either

You can know if an Either isLeft or isRight. You can also do pattern matching on it (which is the best way of working with objects of this type).

Either<UnderageError, Alcoholic> alcoholicEither = //...
Match(alcoholicEither).of(
        Case($Right($()), c -> "drinked " + c),
        Case($Left($()), t -> "troubles")
        );

Once you have an Either, you can call map on it:

Either<UnderageError,Integer> concentrationOrError = alcoholicEither.map(Alcoholic::getAlcoholConcentration);

If it’s called on a Right, the value inside it will be transformed. If it’s a Left, that will be returned unchanged.

We can also change the Right success convention using projections:

If the given Either is a Right and projected to a Left, the Left operations have no effect on the Right value. If the given Either is a Left and projected to a Right, the Right operations have no effect on the Left value. If a Left is projected to a Left or a Right is projected to a Right, the operations have an effect.

Either<String, Alcoholic> concentrationOrError = alcoholicEither.left().map(UnderageError::getReason).toEither();

Other Features

We can fold Left and Right to one common type:

String reasonOrAlcoholic = alcoholicEither.fold(UnderageError::getReason, Alcoholic::toString);

or swap sides:

Either<Alcoholic, UnderageError> swap = alcoholicEither.swap();

Either supports all the higher-order methods: map ( and mapLeft), flatMap, filter, forEach, …

Either as an Exception replacement

One of the nice features of Java exceptions is the ability to declare several different potential exception types as part of a method signature. Either can do that as well! And without the exception goto style handling and performance penalties:

public interface Error {
    String getReason();
}
public class UnderageError implements Error {
    private final String reason;
    @Override public String getReason() {
        return reason;
    }
}
public class ImpossibleError implements Error {
    @Override public String getReason() {
        return "Black magic happened";
    }
}
Either<Error, Alcoholic> alcoholicEither = //...
Match(alcoholicEither).of(
        Case($Right($()), c -> "drinked " + c),
        Case($Left($(instanceOf(UnderageError.class))), t -> "troubles"),
        Case($Left($(instanceOf(ImpossibleError.class))), t -> "wizard")
        );

Nice and smooth as silk :smile:

Conclusions and thoughts

When you learn a new paradigm, you need to reconsider all the familiar ways of solving problems. Functional programming uses different idioms to report error conditions! Either is a type that is not without flaws, and whether you want to have to deal with them and incorporate it in your own code is ultimately up to you.

Memento: throwing an exception will break your functional composition and probably result in unexpected behaviour for the caller of your function. So it should be reserved as a method of last resort, for when the other options don’t make sense.

Comments