Either¶
Either<L, R> is one of two values: Left(L value) or Right(R value). By convention the left side is the error
and the right side the result.
Either is right-biased: map, flatMap and most other members work on the right value and pass a Left through
unchanged.
When to use it¶
Use Either for a step that can fail with an error you define, when the first error stops the work.
- If the checks are independent and you want every error at once, use
Validation. - If the error is an exception thrown by the code you call, use
Try. - If there is nothing to say about the failure,
Optionis enough.
Construction¶
var right = Either.<String, Integer>right(42); // Either<String, Integer>
var left = Either.<String, Integer>left("not a number"); // Either<String, Integer>
var checked = Either.fromPredicate(-1, n -> n >= 0, n -> "negative: " + n); // Either<String, Integer>
// Right(42), Left(not a number), Left(negative: -1)
fromPredicate keeps the value as a Right when the test holds. Otherwise the last function turns the rejected
value into the Left, so the error can name it; write _ -> "negative" when it doesn't need to.
Pattern matching over the cases¶
Left and Right are records, so pattern matching with a switch expression needs no default:
var result = Either.<String, Integer>right(42);
var text = switch (result) {
case Right(var value) -> "got " + value;
case Left(var error) -> "failed: " + error;
};
// "got 42"
fold(ifLeft, ifRight) does the same with two functions.
Operations¶
Chaining steps¶
flatMap runs the next step only on a Right. mapLeft transforms the error.
var total = Either.<String, Integer>right(2)
.flatMap(n -> n > 0 ? Either.right(n * 10) : Either.left("not positive"))
.mapLeft(error -> "rejected: " + error); // Either<String, Integer>
// Right(20)
Rejecting a value¶
filterOrElse turns a Right that fails a test into a Left, built from the rejected value. flip swaps the two
sides.
var adult = Either.<String, Integer>right(15)
.filterOrElse(n -> n >= 18, n -> n + " is under 18"); // Either<String, Integer>
var message = adult.fold(error -> "rejected: " + error, n -> "accepted: " + n); // String
var flipped = adult.flip(); // Either<Integer, String>
// Left(15 is under 18), "rejected: 15 is under 18", Right(15 is under 18)
Other members¶
mapBoth(leftMapper, rightMapper)transforms whichever side is present.getOrElse(Function)builds a result from the left value;getOrElseThrow(Function)builds an exception from it.tapruns an action on a right value;tapLefton a left one.isLeft(),isRight()andgetLeft()read the case directly.zipandzipWithcombine severalEithers and stop at the firstLeft; see zip at arity N.
Conversions¶
toOption()keeps the right value and drops the left one.toTry(Function)builds theFailurecause from the left value.toValidation()givesValidor anInvalidwith one error.toVector()gives zero or one element.
var missing = Either.<String, Integer>left("missing");
var option = missing.toOption(); // Option<Integer>
var attempt = missing.toTry(IllegalArgumentException::new); // Try<Integer>
var validation = missing.toValidation(); // Validation<String, Integer>
// None, Failure(java.lang.IllegalArgumentException: missing), Invalid(missing)
Sharp edges¶
- Neither side holds
null:Either.left(null)andEither.right(null)throw aNullPointerException. get()on aLeftthrows aNoSuchElementException. Prefer pattern matching,foldorgetOrElse.- There is no
filter: a rejected value needs a left value, so the method isfilterOrElse. toTryalways takes a function, even when the left side is already aThrowable; passt -> t.Eitherstops at the firstLeft. To report every error, useValidation.