* Mon ** TypeAlias, Maybe and Result for Java. [[https://oss.sonatype.org/content/repositories/releases/net/kemitix/mon][file:https://img.shields.io/nexus/r/https/oss.sonatype.org/net.kemitix/mon.svg?style=for-the-badge]] [[https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22net.kemitix%22%20AND%20a%3A%22mon%22][file:https://img.shields.io/maven-central/v/net.kemitix/mon.svg?style=for-the-badge]] [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://img.shields.io/sonar/https/sonarcloud.io/net.kemitix%3Amon/coverage.svg?style=for-the-badge#.svg]] [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://img.shields.io/sonar/https/sonarcloud.io/net.kemitix%3Amon/tech_debt.svg?style=for-the-badge#.svg]] [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=sqale_rating#.svg]] [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=alert_status#.svg]] [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=reliability_rating#.svg]] [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=security_rating#.svg]] [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=sqale_index#.svg]] [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=vulnerabilities#.svg]] [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=bugs#.svg]] [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=code_smells#.svg]] [[https://sonarcloud.io/dashboard?id=net.kemitix%3Amon][file:https://sonarcloud.io/api/project_badges/measure?project=net.kemitix%3Amon&metric=ncloc#.svg]] [[https://app.codacy.com/project/kemitix/mon/dashboard][file:https://img.shields.io/codacy/grade/d57096b0639d496aba9a7e43e7cf5b4c.svg?style=for-the-badge]] [[http://i.jpeek.org/net.kemitix/mon/index.html][file:http://i.jpeek.org/net.kemitix/mon/badge.svg]] - [Maven Usage] - [TypeAlias] - [Maybe] - [Result] - [Tree] - [Lazy] - [Either] ** Maven Usage #+BEGIN_SRC xml net.kemitix mon RELEASE #+END_SRC The latest version should be shown above with the nexus and maven-central badges or can be found on [[https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22net.kemitix%22%20AND%20a%3A%22mon%22][Maven Central]]. ** TypeAlias In Haskell it is possible to create an alias for a Type, and to then use that alias with the same behaviour as the original, except that the compiler doesn't treat the alias as the same Type and will generate compiler errors if you try and use them together. e.g.: #+BEGIN_SRC haskell type PhoneNumber = String type Name = String type PhoneBook = [(Name,PhoneNumber)] #+END_SRC In Java we don't have the ability to have that true alias, so TypeAlias is more of a type-wrapper. It's as close as I could get to a Haskell type alias in Java. The benefits of using TypeAlias are: - encapsulation of the wrapped type when passing references through code that doesn't need to access the actual value, but only to pass it on - type-safe parameters where you would otherwise be passing Strings, Integers, Lists, or other general classes - equality and hashcode - less verbose than implementing your own *TypeAlias Example:* #+BEGIN_SRC java class PhoneNumber extends TypeAlias { private PhoneNumber(final String value) { super(value); } public static PhoneNumber of(final String phoneNumber) { return new PhoneNumber(phoneNumber); } } #+END_SRC *Roll your own:* #+BEGIN_SRC java class PhoneNumber { private final String value; private PhoneNumber(final String value) { this.value = value; } public static PhoneNumber of(final String phoneNumber) { return new PhoneNumber(phoneNumber); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PhoneNumber that = (PhoneNumber) o; return Objects.equals(value, that.value); } @Override public int hashCode() { return Objects.hash(value); } public String getValue() { return value; } } #+END_SRC *Lombok:* Although, if you are using Lombok, that can be equally terse, both it and TypeAlias coming in at 8 lines each, compared to 24 for rolling your own: #+BEGIN_SRC java @Value @RequiredArgsConstructor(access = AccessLevel.PRIVATE) class PhoneNumber { private final String value; public static PhoneNumber of(final String phoneNumber) { return new PhoneNumber(phoneNumber); } } #+END_SRC *** =TypeAlias= *can* be a Monad #+BEGIN_SRC java package net.kemitix.mon; import org.assertj.core.api.WithAssertions; import org.junit.Test; import java.util.function.Function; public class TypeAliasMonadTest implements WithAssertions { private final int v = 1; private final Function> f = i -> a(i * 2); private final Function> g = i -> a(i + 6); private static AnAlias a(Integer v) { return AnAlias.of(v); } @Test public void leftIdentity() { assertThat( a(v).flatMap(f) ).isEqualTo( f.apply(v) ); } @Test public void rightIdentity_inline() { // java isn't able to properly infer the correct types when used in-line assertThat( a(v).>flatMap(x -> a(x)) ).isEqualTo( a(v) ); } @Test public void rightIdentity_explicitValue() { final AnAlias integerAnAlias = a(v).flatMap(x -> a(x)); assertThat( integerAnAlias ).isEqualTo( a(v) ); } @Test public void associativity() { assertThat( a(v).flatMap(f).flatMap(g) ).isEqualTo( a(v).flatMap(x -> f.apply(x).flatMap(g)) ); } static class AnAlias extends TypeAlias { private AnAlias(T value) { super(value); } static AnAlias of(T value) { return new AnAlias<>(value); } } } #+END_SRC *** Instance Methods **** =final R map(final Function f)= Map the TypeAlias into another value. #+BEGIN_SRC java final StudentId studentId = StudentId.of(123); final String idString = studentId.map(id -> String.valueOf(id)); class StudentId extends TypeAlias { private StudentId(Integer value) { super(value); } static StudentId of(Integer id) { return new StudentId(id); } } #+END_SRC **** =final > U flatMap(final Function f)= Map the TypeAlias into another TypeAlias. #+BEGIN_SRC java final StudentId studentId = StudentId.of(123); final StudentName studentName = studentId.flatMap(id -> getStudentName(id)); class StudentName extends TypeAlias { private StudentName(String value) { super(value); } static StudentName of(final String name) { return new StudentName(name); } } #+END_SRC **** =T getValue()= Get the value of the TypeAlias. #+BEGIN_SRC java final String name = studentName.getValue(); #+END_SRC ** Maybe Allows specifying that a value may or may not be present. Similar to =Optional=. =Maybe= provides additional methods that =Optional= doesn't: =isNothing()=, =stream()=, =ifNothing()= and =match()=. =Maybe= does not have a =get()= method. Unlike =Optional=, when a =map()= results in a =null=, the =Maybe= will continue to be a =Just=. =Optional= would switch to being empty. [[http://blog.vavr.io/the-agonizing-death-of-an-astronaut/][vavi.io follows the same behaviour as =Maybe=]]. #+BEGIN_SRC java import net.kemitix.mon.maybe.Maybe; import java.util.function.Function; import java.util.function.Predicate; class MaybeExample { public static void main(String[] args) { Maybe.just(countArgs(args)) .filter(isEven()) .map(validMessage()) .match( just -> System.out.println(just), () -> System.out.println("Not an valid value") ); } private static Function validMessage() { return v -> String.format("Value %d is even", v); } private static Predicate isEven() { return v -> v % 2 == 0; } private static Integer countArgs(String[] args) { return args.length; } } #+END_SRC In the above example, the number of command line arguments are counted, if there are an even number of them then a message is created and printed by the Consumer parameter in the =match= call. If there is an odd number of arguments, then the filter will return =Maybe.nothing()=, meaning that the =nothing= drops straight through the map and triggers the Runnable parameter in the =match= call. *** =Maybe= is a Monad: #+BEGIN_SRC java package net.kemitix.mon; import net.kemitix.mon.maybe.Maybe; import org.assertj.core.api.WithAssertions; import org.junit.Test; import java.util.function.Function; public class MaybeMonadTest implements WithAssertions { private final int v = 1; private final Function> f = i -> m(i * 2); private final Function> g = i -> m(i + 6); private static Maybe m(int value) { return Maybe.maybe(value); } @Test public void leftIdentity() { assertThat( m(v).flatMap(f) ).isEqualTo( f.apply(v) ); } @Test public void rightIdentity() { assertThat( m(v).flatMap(x -> m(x)) ).isEqualTo( m(v) ); } @Test public void associativity() { assertThat( m(v).flatMap(f).flatMap(g) ).isEqualTo( m(v).flatMap(x -> f.apply(x).flatMap(g)) ); } } #+END_SRC *** Static Constructors **** =static Maybe maybe(T value)= Create a Maybe for the value that may or may not be present. Where the value is =null=, that is taken as not being present. #+BEGIN_SRC java final Maybe just = Maybe.maybe(1); final Maybe nothing = Maybe.maybe(null); #+END_SRC **** =static Maybe just(T value)= Create a Maybe for the value that is present. The =value= must not be =null= or a =NullPointerException= will be thrown. If you can't prove that the value won't be =null= you should use =Maybe.maybe(value)= instead. #+BEGIN_SRC java final Maybe just = Maybe.just(1); #+END_SRC **** =static Maybe nothing()= Create a Maybe for a lack of a value. #+BEGIN_SRC java final Maybe nothing = Maybe.nothing(); #+END_SRC *** Instance Methods **** =Maybe filter(Predicate predicate)= Filter a Maybe by the predicate, replacing with Nothing when it fails. #+BEGIN_SRC java final Maybe maybe = Maybe.maybe(getValue()) .filter(v -> v % 2 == 0); #+END_SRC **** = Maybe map(Function f)= Applies the function to the value within the Maybe, returning the result within another Maybe. #+BEGIN_SRC java final Maybe maybe = Maybe.maybe(getValue()) .map(v -> v * 100); #+END_SRC **** = Maybe flatMap(Function> f)= Applies the function to the value within the =Maybe=, resulting in another =Maybe=, then flattens the resulting =Maybe>= into =Maybe=. Monad binder maps the Maybe into another Maybe using the binder method f #+BEGIN_SRC java final Maybe maybe = Maybe.maybe(getValue()) .flatMap(v -> Maybe.maybe(getValueFor(v))); #+END_SRC **** =void match(Consumer just, Runnable nothing)= Matches the Maybe, either just or nothing, and performs either the Consumer, for Just, or Runnable for nothing. #+BEGIN_SRC java Maybe.maybe(getValue()) .match( just -> workWithValue(just), () -> nothingToWorkWith() ); #+END_SRC **** =T orElse(T otherValue)= A value to use when Maybe is Nothing. #+BEGIN_SRC java final Integer value = Maybe.maybe(getValue()) .orElse(1); #+END_SRC **** =T orElseGet(Supplier otherValueSupplier)= Provide a value to use when Maybe is Nothing. #+BEGIN_SRC java final Integer value = Maybe.maybe(getValue()) .orElseGet(() -> getDefaultValue()); #+END_SRC **** =T or(Supplier alternative)= Provide an alternative Maybe to use when Maybe is Nothing. #+BEGIN_SRC java final Maybe value = Maybe.maybe(getValue()) .or(() -> Maybe.just(defaultValue)); #+END_SRC **** =void orElseThrow(Supplier error)= Throw the exception if the Maybe is a Nothing. #+BEGIN_SRC java final Integer value = Maybe.maybe(getValue()) .orElseThrow(() -> new RuntimeException("error")); #+END_SRC **** =Maybe peek(Consumer consumer)= Provide the value within the Maybe, if it exists, to the Consumer, and returns this Maybe. Conceptually equivalent to the idea of =ifPresent(...)=. #+BEGIN_SRC java final Maybe maybe = Maybe.maybe(getValue()) .peek(v -> v.foo()); #+END_SRC **** =void ifNothing(Runnable runnable)= Run the runnable if the Maybe is a Nothing, otherwise do nothing. #+BEGIN_SRC java Maybe.maybe(getValue()) .ifNothing(() -> doSomething()); #+END_SRC **** =Stream stream()= Converts the Maybe into either a single value stream or an empty stream. #+BEGIN_SRC java final Stream stream = Maybe.maybe(getValue()) .stream(); #+END_SRC **** =boolean isJust()= Checks if the Maybe is a Just. #+BEGIN_SRC java final boolean isJust = Maybe.maybe(getValue()) .isJust(); #+END_SRC **** =boolean isNothing()= Checks if the Maybe is Nothing. #+BEGIN_SRC java final boolean isNothing = Maybe.maybe(getValue()) .isNothing(); #+END_SRC **** =Optional toOptional()= Convert the Maybe to an Optional. #+BEGIN_SRC java final Optional optional = Maybe.maybe(getValue()) .toOptional(); #+END_SRC ** Result Allows handling error conditions without the need to catch exceptions. When a =Result= is returned from a method it will contain one of two values. Either the actual result, or an error in the form of an =Exception=. The exception is returned within the =Result= and is not thrown. #+BEGIN_SRC java import net.kemitix.mon.result.Result; import java.io.IOException; class ResultExample implements Runnable { public static void main(final String[] args) { new ResultExample().run(); } @Override public void run() { Result.of(() -> callRiskyMethod()) .flatMap(state -> doSomething(state)) .match( success -> System.out.println(success), error -> error.printStackTrace() ); } private String callRiskyMethod() throws IOException { return "I'm fine"; } private Result doSomething(final String state) { return Result.of(() -> state + ", it's all good."); } } #+END_SRC In the above example the string ="I'm fine"= is returned by =callRiskyMethod()= within a successful =Result=. The =.flatMap()= call, unwraps that =Result= and, as it is a success, passes the contents to =doSomething()=, which in turn returns a =Result= that the =.flatMap()= call returns. =match()= is called on the =Result= and, being a success, will call the success =Consumer=. Had =callRiskyMethod()= thrown an exception it would have been caught by the =Result.of()= method which would have then been an error =Result=. An error Result would have ignored the =flatMap= and skipped to the =match()= when it would have called the error =Consumer=. *** =Result= is a Monad #+BEGIN_SRC java package net.kemitix.mon; import net.kemitix.mon.result.Result; import org.assertj.core.api.WithAssertions; import org.junit.Test; import java.util.function.Function; public class ResultMonadTest implements WithAssertions { private final int v = 1; private final Function> f = i -> r(i * 2); private final Function> g = i -> r(i + 6); private static Result r(int v) { return Result.ok(v); } @Test public void leftIdentity() { assertThat( r(v).flatMap(f) ).isEqualTo( f.apply(v) ); } @Test public void rightIdentity() { assertThat( r(v).flatMap(x -> r(x)) ).isEqualTo( r(v) ); } @Test public void associativity() { assertThat( r(v).flatMap(f).flatMap(g) ).isEqualTo( r(v).flatMap(x -> f.apply(x).flatMap(g)) ); } } #+END_SRC *** Static Constructors **** =static Result of(Callable callable)= Create a Result for a output of the Callable. If the Callable throws and Exception, then the Result will be an error and will contain that exception. This will be the main starting point for most Results where the callable could throw an =Exception=. #+BEGIN_SRC java final Result okay = Result.of(() -> 1); final Result error = Result.of(() -> {throw new RuntimeException();}); #+END_SRC **** =static Result ok(T value)= Create a Result for a success. Use this where you have a value that you want to place into the Result context. #+BEGIN_SRC java final Result okay = Result.ok(1); #+END_SRC **** =static Result error(Throwable error)= Create a Result for an error. #+BEGIN_SRC java final Result error = Result.error(new RuntimeException()); #+END_SRC *** Static Methods These static methods provide integration with the =Maybe= class. #+BEGIN_SRC java #+END_SRC **** =static Maybe toMaybe(Result result)= Creates a =Maybe= from the =Result=, where the =Result= is a success, then the =Maybe= will contain the value. However, if the =Result= is an error then the =Maybe= will be nothing. #+BEGIN_SRC java final Result result = Result.of(() -> getValue()); final Maybe maybe = Result.toMaybe(result); #+END_SRC **** =static Result fromMaybe(Maybe maybe, Supplier error)= Creates a =Result= from the =Maybe=, where the =Result= will be an error if the =Maybe= is nothing. Where the =Maybe= is nothing, then the =Supplier= will provide the error for the =Result=. #+BEGIN_SRC java final Maybe maybe = Maybe.maybe(getValue()); final Result result = Result.fromMaybe(maybe, () -> new NoSuchFileException("filename")); #+END_SRC **** =static Result> invert(Maybe> maybeResult)= Swaps the =Result= within a =Maybe=, so that =Result= contains a =Maybe=. #+BEGIN_SRC java final Maybe> maybe = Maybe.maybe(Result.of(() -> getValue())); final Result> result = Result.invert(maybe); #+END_SRC **** =static Result> flatMapMaybe(Result> maybeResult, Function,Result>> f)= Applies the function to the contents of a Maybe within the Result. #+BEGIN_SRC java final Result> result = Result.of(() -> Maybe.maybe(getValue())); final Result> maybeResult = Result.flatMapMaybe(result, maybe -> Result.of(() -> maybe.map(v -> v * 2))); #+END_SRC *** Instance Methods **** = Result map(Function f)= Applies the function to the value within the Functor, returning the result within a Functor. #+BEGIN_SRC java final Result result = Result.of(() -> getValue()) .map(v -> String.valueOf(v)); #+END_SRC **** = Result flatMap(Function> f)= Returns a new Result consisting of the result of applying the function to the contents of the Result. #+BEGIN_SRC java final Result result = Result.of(() -> getValue()) .flatMap(v -> Result.of(() -> String.valueOf(v))); #+END_SRC **** = Result andThen(Function> f)= Maps a Success Result to another Result using a Callable that is able to throw a checked exception. #+BEGIN_SRC java final Result result = Result.of(() -> getValue()) .andThen(v -> () -> {throw new IOException();}); #+END_SRC **** =void match(Consumer onSuccess, Consumer onError)= Matches the Result, either success or error, and supplies the appropriate Consumer with the value or error. #+BEGIN_SRC java Result.of(() -> getValue()) .match( success -> System.out.println(success), error -> System.err.println("error") ); #+END_SRC **** =Result recover(Function> f)= Provide a way to attempt to recover from an error state. #+BEGIN_SRC java final Result result = Result.of(() -> getValue()) .recover(e -> Result.of(() -> getSafeValue(e))); #+END_SRC **** =Result peek(Consumer consumer)= Provide the value within the Result, if it is a success, to the Consumer, and returns this Result. #+BEGIN_SRC java final Result result = Result.of(() -> getValue()) .peek(v -> System.out.println(v)); #+END_SRC **** =Result thenWith(Function> f)= Perform the continuation with the current Result value then return the current Result, assuming there was no error in the continuation. #+BEGIN_SRC java final Result result = Result.of(() -> getValue()) .thenWith(v -> () -> System.out.println(v)) .thenWith(v -> () -> {throw new IOException();}); #+END_SRC **** =Result> maybe(Predicate predicate)= Wraps the value within the Result in a Maybe, either a Just if the predicate is true, or Nothing. #+BEGIN_SRC java final Result> result = Result.of(() -> getValue()) .maybe(v -> v % 2 == 0); #+END_SRC **** =T orElseThrow()= Extracts the successful value from the result, or throws the error =Throwable=. #+BEGIN_SRC java final Integer result = Result.of(() -> getValue()) .orElseThrow(); #+END_SRC **** = T orElseThrow(Class type) throws E= Extracts the successful value from the result, or throws the error when it is of the given type. Any other errors will be thrown inside an =UnexpectedErrorResultException=. #+BEGIN_SRC java final Integer result = Result.of(() -> getValue()) .orElseThrow(IOException.class); #+END_SRC **** =T orElseThrowUnchecked()= Extracts the successful value from the result, or throws the error within an =ErrorResultException=. #+BEGIN_SRC java final Integer result = Result.of(() -> getValue()) .orElseThrowUnchecked(); #+END_SRC **** =void onError(Consumer errorConsumer)= A handler for error states. #+BEGIN_SRC java Result.of(() -> getValue()) .onError(e -> handleError(e)); #+END_SRC **** =boolean isOkay()= Checks if the Result is a success. #+BEGIN_SRC java final boolean isOkay = Result.of(() -> getValue()) .isOkay(); #+END_SRC **** =boolean isError()= Checks if the Result is an error. #+BEGIN_SRC java final boolean isError = Result.of(() -> getValue()) .isError(); #+END_SRC ** Tree A Generalised tree, where each node may or may not have an item, and may have any number of sub-trees. Leaf nodes are Trees with zero sub-trees. *** Static Constructors **** =static Tree leaf(R item)= Create a leaf containing the item. The leaf has no sub-trees. #+BEGIN_SRC java final Tree tree = Tree.leaf("item"); #+END_SRC **** =static Tree of(R item, Collection> subtrees)= Create a tree containing the item and sub-trees. #+BEGIN_SRC java final Tree tree = Tree.of("item", Collections.singletonList(Tree.leaf("leaf")); #+END_SRC *** Instance Methods **** = Tree map(Function f)= Applies the function to the item within the Tree and to all sub-trees, returning a new Tree. #+BEGIN_SRC java final Tree tree = ...; final Tree result = tree.map(UUID::toString); #+END_SRC **** =Optional item()= Returns the contents of the Tree node within an Optional. #+BEGIN_SRC java final Tree tree = ...; final Optional result = tree.item(); #+END_SRC **** =int count()= Returns the total number of items in the tree, including sub-trees. Null items don't count. #+BEGIN_SRC java final Tree tree = ...; final int result = tree.count(); #+END_SRC **** =List subTrees()= Returns a list of sub-trees within the tree. #+BEGIN_SRC java final Tree tree = ...; final List> result = tree.subTrees(); #+END_SRC ** Lazy A Lazy evaluated expression. Using a Supplier to provide the value, only evaluates the value when required, and never more than once. *** Static Constructors **** =static Lazy of(Supplier supplier)= Create a new Lazy value from the supplier. #+BEGIN_SRC java final Suppler supplier = ...; final Lazy lazy = Lazy.of(supplier); #+END_SRC *** Instance Methods **** =boolean isEvaluated()= Checks if the value has been evaluated. #+BEGIN_SRC java final Lazy lazy = ...; final boolean isEvaluated = lazy.isEvaluated(); #+END_SRC **** =T value()= The value, evaluating it if necessary. #+BEGIN_SRC java final Lazy lazy = ...; final UUID value = lazy.value(); #+END_SRC **** = Lazy map(Function f)= Maps the Lazy instance into a new Lazy instance using the function. #+BEGIN_SRC java final Lazy uuidLazy = ...; final Lazy stringLazy = uuidLazy.map(v -> v.toString()); #+END_SRC ** Either Allows handling a value that can be one of two types, a left value/type or a right value/type. When an =Either= is returned from a method it will contain either a left or a right. Where the =Either= is used to represent success/failure, the left case is, by convention, used to indicate the error, and right the success. An alternative is to use the =Result= which more clearly distinguishes success from failure. *** =Either= *is not* a Monad. *** Static Constructors **** =static Either left(final L l)= Create a new Either holding a left value. #+BEGIN_SRC java final Either left = Either.left(getIntegerValue()); #+END_SRC **** =static Either right(final R r)= Create a new Either holding a right value. #+BEGIN_SRC java final Either right = Either.right(getStringValue()); #+END_SRC *** Instance Methods **** =boolean isLeft()= Checks if the Either holds a left value. #+BEGIN_SRC java final boolean leftIsLeft = Either.left(getIntegerValue()).isLeft(); final boolean rightIsLeft = Either.right(getStringValue()).isLeft(); #+END_SRC **** =boolean isRight()= Checks if the Either holds a right value. #+BEGIN_SRC java final boolean leftIsRight = Either.left(getIntegerValue()).isRight(); final boolean rightIsRight = Either.right(getStringValue()).isRight(); #+END_SRC **** =void match(Consumer onLeft, Consumer onRight)= Matches the Either, invoking the correct Consumer. #+BEGIN_SRC java Either.left(getIntegerValue()) .match( left -> handleIntegerValue(left), right -> handleStringValue(right) ); #+END_SRC **** = Either mapLeft(Function f)= Map the function across the left value. #+BEGIN_SRC java final Either either = Either.left(getIntegerValue()) .mapLeft(i -> i.doubleValue()); #+END_SRC **** = Either mapRight(Function f)= Map the function across the right value. #+BEGIN_SRC java final Either either = Either.left(getIntegerValue()) .mapRight(s -> s + "x"); #+END_SRC