Skip to content

Instantly share code, notes, and snippets.

@nithinivi
Created March 26, 2024 12:09
Show Gist options
  • Save nithinivi/5ea7baba6f2273fc76cb605ee673d732 to your computer and use it in GitHub Desktop.
Save nithinivi/5ea7baba6f2273fc76cb605ee673d732 to your computer and use it in GitHub Desktop.
package sg.dlt.indus.lib.functional;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Either Monad represents a value of two possible types. An Either is either a {@link Left} or a
* {@link Right}.By convention the success case is Right and the failure is Left.
*
* @param <L> The type of the Left value of an Either.
* @param <R> The type of the Right value of an Either.
*/
public interface Either<L, R> {
/**
* Matches the value of this Either instance against two functions.
*
* @param left The function to apply if the value is of type A (left).
* @param right The function to apply if the value is of type B (right).
* @param <C> The return type of the functions.
* @return The result of applying the appropriate function to the value
* depends on instance initialization .
*/
<C> C match(Function<? super L, ? extends C> left,
Function<? super R, ? extends C> right);
/**
* Returns whether this Either is a Left.
*
* <pre>{@code
* // prints "true"
* System.out.println(Either.left("error").isLeft());
*
* // prints "false"
* System.out.println(Either.right(42).isLeft());
* }</pre>
*
* @return true, if this is a Left, false otherwise
*/
boolean isLeft();
/**
* Returns whether this Either is a Right.
*
* <pre>{@code
* // prints "true"
* System.out.println(Either.right(42).isRight());
*
* // prints "false"
* System.out.println(Either.left("error").isRight());
* }</pre>
*
* @return true, if this is a Right, false otherwise
*/
boolean isRight();
/**
* Gets the {@code Left} value or throws.
*
* @return the left value, if the underlying {@code Either} is a {@code Left}
* @throws NoSuchElementException if the underlying {@code Either} of this {@code LeftProjection} is a {@code Right}
*/
L getLeft();
/**
* Gets the {@code Right} value or throws.
*
* @return the right value, if the underlying {@code Either} is a {@code Right}
* @throws NoSuchElementException if the underlying {@code Either} of this {@code RightProjection} is a {@code Left}
*/
R get();
/**
* Constructs a {@link Left}
*
* <pre>{@code
* // Creates Either instance initiated with right value 1
* Either<Integer, ? > either = Either.left(1);
* }</pre>
*
* @param left The value.
* @param <L> Type of left value.
* @param <R> Type of right value.
* @return A new {@code Left} instance.
*/
static <L, R> Either<L, R> left(L left) {
return new Left<>(left);
}
/**
* Constructs a {@link Right}
*
* <pre>{@code
* // Creates Either instance initiated with right value 1
* Either<?, Integer> either = Either.right(1);
* }</pre>
*
* @param right The value.
* @param <L> Type of left value.
* @param <R> Type of right value.
* @return A new {@code Right} instance.
*/
static <L, R> Either<L, R> right(R right) {
return new Right<>(right);
}
/**
* Extracts the value of type A if the Either instance holds a value of type A (left).
*
* @return An Optional containing the value of type A if it exists, otherwise an empty Optional.
*/
default Optional<L> fromLeft() {
return this.match(Optional::of, value -> Optional.empty());
}
/**
* Extracts the value of type B if the Either instance holds a value of type B (right).
*
* @return An Optional containing the value of type B if it exists, otherwise an empty Optional.
*/
default Optional<R> fromRight() {
return this.match(value -> Optional.empty(), Optional::of);
}
/**
* Consumes {@link Right} and {@link Left}
*
* <pre>{@code
* List<Integer> intList = new ArrayList<>();
* List<String> stringList = new ArrayList<>();
*
* Either<String, Integer> either
* either.consumer(
* intList::add,
* stringList::add
* )
* }</pre>
*
* @throws NullPointerException if {@code left} or {@code right} is null
*/
default void consume(Consumer<? super L> left,
Consumer<? super R> right) {
Objects.requireNonNull(left, "left is null");
Objects.requireNonNull(right, "right is null");
if (isLeft())
left.accept(getLeft());
else
right.accept(get());
}
/**
* If a value is {@link Right}, performs the given action with the value,
* otherwise does nothing.
*
* @param action the action to be performed, if a {@link Right} is present
* @throws NullPointerException if action is
* {@code null}
*/
default void ifRight(Consumer<R> action) {
Objects.requireNonNull(action, "action is null");
if (isRight()) {
action.accept(get());
}
}
}
class Right<L, R> implements Either<L, R> {
private final R value;
public Right(R value) {
this.value = value;
}
public <C> C match(Function<? super L, ? extends C> left,
Function<? super R, ? extends C> right) {
return right.apply(value);
}
@Override
public boolean isLeft() {
return false;
}
@Override
public boolean isRight() {
return true;
}
@Override
public L getLeft() {
throw new NoSuchElementException("getLeft() on Right");
}
@Override
public R get() {
return value;
}
}
class Left<L, R> implements Either<L, R> {
private final L value;
public Left(L value) {
this.value = value;
}
public <C> C match(Function<? super L, ? extends C> left,
Function<? super R, ? extends C> right) {
return left.apply(value);
}
@Override
public boolean isLeft() {
return true;
}
@Override
public boolean isRight() {
return false;
}
@Override
public L getLeft() {
return value;
}
@Override
public R get() {
throw new NoSuchElementException("get() on Left");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment