Skip to main content

Either - Turn Your Partial Function into a Total Function

Why Either

Either can turn a partial function into a total function.

When your function (or method) is a partial function meaning that it may not have a return value for some inputs, your code becomes hard to understand and maintain because it may throw an Exception when there's no corresponding result for a such input. Throwing an Exception means that there might be any number exceptions that can be thrown from the function, and to see what might be thrown, you have to check the implementation details.

To make it a total function so that it always has the output for the given input, there can be multiple ways but an easy and recommended way is using Either.

NOTE

Either is right-biased meaning that the most well-known combinators on Either (e.g. map, flatMap, etc.) are by default for the Right value. If you want to manipulate the left one, you need to use the combinators for Left which are usually prefixed with left (e.g. leftMap, leftFlatMap, etc.).

Create

Either.right to contain the result

import j8plus.types.Either;

Either.<String, Integer>right(1);
// Either<String, Integer> = Right(1)

Either.right to contain the error

import j8plus.types.Either;

Either.<String, Integer>left("Error message");
// Either<String, Integer> = Left("Error message")

Either from Optional

import j8plus.types.Either;

final Optional<Integer> num = Optional.ofNullable(1);
Either.fromOptional(num, () -> "No number found");
// Either<String, Integer> = Right(1)

final Optional<Integer> noNum = Optional.empty();
Either.fromOptional(noNum, () -> "No number found");
// Either<String, Integer> = Left("No number found")

Either from Maybe

import j8plus.types.Maybe;
import j8plus.types.Either;

final Maybe<Integer> num = Maybe.maybe(1);
Either.fromMaybe(num, () -> "No number found");
// Either<String, Integer> = Right(1)

final Maybe<Integer> noNum = Maybe.nothing();
Either.fromMaybe(noNum, () -> "No number found");
// Either<String, Integer> = Left("No number found")

Transform

Either.map

import j8plus.types.Either;

final Either<String, Integer> errorOrNumber = Either.right(5);
// Either<String, Integer> = Right(5)

errorOrNumber.map(n -> n * 2);
// Either<String, Integer> = Right(10)

Either.flatMap

import j8plus.types.Either;

public Either<String, Integer> foo(int n) {
if (n < 0) {
return Either.left(
"foo can't take a negative int. [n: " + n + "]"
);
} else {
return Either.right(n * 2);
}
}

public Either<String, Integer> bar(int n) {
if (n < 100) {
return Either.left(
"bar can't take an int less than 100. [n: " + n + "]"
);
} else {
return Either.right(n - 100);
}
}

All happy paths

Either.<String, Integer>right(50) // Either<String, Integer> = Right(50)
.flatMap(n -> foo(n)) // Either<String, Integer> = Right(100)
.flatMap(n -> bar(n)) // Either<String, Integer> = Right(0)
// Or with method references
Either.<String, Integer>right(50) // Either<String, Integer> = Right(50)
.flatMap(this::foo) // Either<String, Integer> = Right(100)
.flatMap(this::bar) // Either<String, Integer> = Right(0)

Unhappy path cases

Either.<String, Integer>right(-1) // Either<String, Integer> = Right(-1)
.flatMap(n -> foo(n)) // Either<String, Integer> = Left("foo can't take a negative int. [n: -1]")
.flatMap(n -> bar(n)); // Either<String, Integer> = Left("foo can't take a negative int. [n: -1]")

// Or with method references
Either.<String, Integer>right(-1) // Either<String, Integer> = Right(-1)
.flatMap(this::foo) // Either<String, Integer> = Left("foo can't take a negative int. [n: -1]")
.flatMap(this::bar); // Either<String, Integer> = Left("foo can't take a negative int. [n: -1]")
Either.<String, Integer>right(5) // Either<String, Integer> = Right(5)
.flatMap(n -> foo(n)) // Either<String, Integer> = Right(10)
.flatMap(n -> bar(n)); // Either<String, Integer> = Left("bar can't take an int less than 100. [n: 10]")

// Or with method references
Either.<String, Integer>right(5) // Either<String, Integer> = Right(5)
.flatMap(this::foo) // Either<String, Integer> = Right(10)
.flatMap(this::bar); // Either<String, Integer> = Left("bar can't take an int less than 100. [n: 10]")

Either.leftMap

If you want to change the left value, you can use map to do it.

import j8plus.types.Either;

public class MyError {
public final String message;
public MyError(final String message) {
this.message = message;
}

@Override
public String toString() {
return "MyError(message=" + message + ")";
}
}

final Either<String, Integer> errorMsgOrNum = Either.left("Error message");
// Either<String, Integer> = Left(Error message)

errorMsgOrNum.leftMap(err -> new MyError(err));
// Either<MyError, Integer> = Left(MyError(message=Error message))

Either.leftFlatMap

Would you like to place the Left value with another Either which can be another Left or even Right? You can use leftFlatMap.

Left to Right

import j8plus.types.Either;

final Either<String, Integer> errorMsgOrNum = Either.left("Error message");
// Either<String, Integer> = Left(Error message)

errorMsgOrNum.leftFlatMap(err -> Either.right(0));
// Either<String, Integer> = Right(0)

Or it can be another Left like

import j8plus.types.Either;

public class MyError {
public final String message;
public MyError(final String message) {
this.message = message;
}

@Override
public String toString() {
return "MyError(message=" + message + ")";
}
}

final Either<String, Integer> errorMsgOrNum = Either.left("Error message");
// Either<String, Integer> = Left(Error message)

errorMsgOrNum.leftFlatMap(err -> Either.left(new MyError(err)));
// Either<MyError, Integer> = Left(MyError(message=Error message))

Either.swap

If you want to swap Left and Right? You can use Either.swap().

import j8plus.types.Either;

final Either<String, Integer> errorMsgOrNum = Either.right(1);
// Either<String, Integer> = Right(1)

errorMsgOrNum.swap();
// Either<Integer, String> = Left(1)
import j8plus.types.Either;

final Either<String, Integer> errorMsgOrNum = Either.left("Error message");
// Either<String, Integer> = Left(Error message)

errorMsgOrNum.swap();
// Either<Integer, String> = Right(Error message)

Get the Value

Either.fold

If you want to get the value inside Either, you can use Either.fold. Since you can have only one result type, you have to provide ways to turn a type in Right and a type in Left into one result type. So fold takes two functions. One for the Right case and the other for the Left case.

The Right case,

import j8plus.types.Either;

final Either<String, Integer> errorMsgOrNum = Either.right(999);
// Either<String, Integer> = Right(999)

errorMsgOrNum.fold(err -> 0, n -> n);
// Integer = 999

// Or using the identity function from java.util.function.Function
import java.util.function.Function;

errorMsgOrNum.fold(err -> 0, Function.identity());
// Integer = 999

The Left case,

import j8plus.types.Either;

final Either<String, Integer> errorMsgOrNum = Either.left("Error message");
// Either<String, Integer> = Left(Error message)

errorMsgOrNum.fold(err -> 0, n -> n);
// Integer = 0

You can also turn the value into a completely different type.

import j8plus.types.Either;
import java.time.Instant;

final Either<String, Long> errorMsgOrMillis = Either.right(1638316800000L);
// Either<String, Long> = Right(1638316800000L)

errorMsgOrMillis.fold(err -> Instant.now(), millis -> Instant.ofEpochMilli(millis));
// Instant = 2021-12-01T00:00:00Z

// Or using method reference
errorMsgOrMillis.fold(err -> Instant.now(), Instant::ofEpochMilli);
// Instant = 2021-12-01T00:00:00Z
import j8plus.types.Either;
import java.time.Instant;

final Either<String, Long> errorMsgOrMillis = Either.left("Error message");
// Either<String, Long> = Left(Error message)

errorMsgOrMillis.fold(err -> Instant.now(), millis -> Instant.ofEpochMilli(millis));
// Instant = 2021-12-30T09:15:24.033117Z

Either.getOrElse

To get the Right value, you can use getOrElse(Supplier<AlternativeValue>). If the Either is Left, it returns the given alternative from the supplier param.

Right case,

import j8plus.types.Either;

final Either<String, Integer> errorMsgOrNum = Either.right(999);
// Either<String, Integer> = Right(999)

errorMsgOrNum.getOrElse(() -> 0);
// Integer = 999

Left case,

import j8plus.types.Either;

final Either<String, Integer> errorMsgOrNum = Either.left("Error message");
// Either<String, Integer> = Left(Error message)

errorMsgOrNum.getOrElse(() -> 0);
// Integer = 0

Either.getLeftOrElse

To get the Left value, you can use getLeftOrElse(Supplier<AlternativeValue>). If the Either is Right, it returns the given alternative from the supplier param.

Right case,

import j8plus.types.Either;

final Either<String, Integer> errorMsgOrNum = Either.right(999);
// Either<String, Integer> = Right(999)

errorMsgOrNum.getLeftOrElse(() -> "Default");
// String = Default

Left case,

import j8plus.types.Either;

final Either<String, Integer> errorMsgOrNum = Either.left("Error message");
// Either<String, Integer> = Left(Error message)

errorMsgOrNum.getLeftOrElse(() -> "Default");
// String = Error message

Use Value

Either.forEach

If you want to use the Right value and don't need to return anything, you can use forEach.

import j8plus.types.Either;

final Either<String, Integer> errorMsgOrNum = Either.right(999);
// Either<String, Integer> = Right(999)

errorMsgOrNum.forEach(n -> System.out.println("The value is " + n));
// The value is 999

Either.forEachLeft

If you want to use the Left value and don't need to return anything, you can use forEachLeft.

import j8plus.types.Either;

final Either<String, Integer> errorMsgOrNum = Either.left("Error message");
// Either<String, Integer> = Left(Error message)

errorMsgOrNum.forEachLeft(err -> System.out.println("ERROR: " + err));
// ERROR: Error message

Check Value in Either

If you want to chekc if Either is Right or Left, you can use isRight() and isLeft() respectively.

Either.isRight

To check if the Either is Right, use isRight(),

import j8plus.types.Either;

final Either<String, Integer> errorMsgOrNum = Either.right(999);
// Either<String, Integer> = Right(999)

errorMsgOrNum.isRight();
// boolean = true

final Either<String, Integer> errorMsgOrNum2 = Either.left("Error message");
// Either<String, Integer> = Left(Error message)

errorMsgOrNum2.isRight();
// boolean = false

Either.isLeft

To check if the Either is Left, use isLeft(),

import j8plus.types.Either;

final Either<String, Integer> errorMsgOrNum = Either.right(999);
// Either<String, Integer> = Right(999)

errorMsgOrNum.isLeft();
// boolean = false

final Either<String, Integer> errorMsgOrNum2 = Either.left("Error message");
// Either<String, Integer> = Left(Error message)

errorMsgOrNum2.isLeft();
// boolean = true

Example

import j8plus.types.Either;

public class DivisionByZeroError {
public final int dividend;

public DivisionByZeroError(final int dividend) {
this.dividend = dividend;
}

public String getMessage() {
return "Arithmetic error: dividing " + dividend + " by zero is not possible.";
}

@Override
public String toString() {
return new StringBuilder("DivisionByZeroError(")
.append("dividend=").append(dividend)
.append(')')
.toString();
}
}

public Either<DivisionByZeroError, Integer> divide(int a, int b) {
if (b == 0) {
return Either.left(new DivisionByZeroError(a));
} else {
return Either.right(a / b);
}
}

final var result1 = divide(10, 2);
// Either<DivisionByZeroError, Integer> = Right(5)

final var result2 = divide(10, 0);
// Either<DivisionByZeroError, Integer> = Left(DivisionByZeroError(dividend=10))