This project is no longer being maintained, and its usage should be replaced with Vavr (http://www.vavr.io)
This project adds some concepts that didn't ship with java 8 lambda project like Either<L,R> or Pair<L,R> etc...
<dependency>
<groupId>no.finn.lambda</groupId>
<artifactId>lambda-companion</artifactId>
<version>0.27</version>
</dependency>
The Either
class provides a way to enforce failure handling in the same fashion as Optional
is a way to enforce null
values handling.
Convention dictates that Left
is used for failure path and Right
is used for success / happy path.
For a failure:
final Either<String, String> var = Either.left("My failure text");
For a success:
final Either<String, String> var = Either.right("My text");
There is no way to directly access the value inside an Either
in the same way as Optional.get()
.
This is made on purpose to avoid code in the style of if(either.isRight()) { either.getRight(); }
which promotes not handling the failure cases (left side).
final Either<String, String> var = ...;
final String myValue = either.fold(failure -> handleFailure(failure), Function.identity())
For the Left
side:
final Either<String, String> var = ...;
final Either<Integer, String> transformed = var.left().map(Integer::valueOf);
For the Right
side:
final Either<String, String> var = ...;
final Either<String, Integer> transformed = var.right().map(Integer::valueOf);
final Logger LOGGER = ...;
final Either<Exception, String> either = ...;
final String myValue = either.left().peek(LOGGER::warn)
.fold(failure -> handleFailure(failure), Function.identity());
final Logger LOGGER = ...;
final Either<Exception, String> either1 = ...;
final Either<Exception, String> either2 = ...;
final Either<Exception, String> either3 = ...;
Arrays.asList(either1, either2, either3).stream().forEach(either -> either.forEach(LOGGER::warn));
private final Either<Exception, String> getUserInput() {
...
}
private final Either<Exception, Integer> processUserInput(final String userInput) {
...
}
private final Either<Exception, Integer> saveUserInput(final Integer userInput) {
...
}
// processUserInput() only gets executed if getUserInput() succeeded, and saveUserInput() only gets executed if processUserInput() succeeded
final Either<Exception, Integer> chained = getUserInput().joinRight(this::processUserInput).joinRight(this::saveUserInput);
private final Integer processValues(final String val1, final String val2, final String val3) {
...
}
final Either<Exception, String> either1 = ...;
final Either<Exception, String> either2 = ...;
final Either<Exception, String> either3 = ...;
Either<Exception, Integer> result = either1.joinRight(val1 -> either2.joinRight(val2 -> either3.right().map(val3 -> processValues(val1, val2, val3))));
It happens (rather often) to end with a Stream
containing Optional
-s and to be willing to only retain Present
instances.
In Java 8 there are 2 options:
- the bad: filtering with
isPresent()
and unwrapping values withget()
:stream().filter(Optional::isPresent).map(Optional::get)
- and the ugly: flat-mapping
Optional
values toStream
of one or no value:stream().flatMap(opt -> opt.map(Stream::of).orElseGet(Optional::empty))
StreamableOptional is a cleaner shorthand for the second solution above (provided that you use StreamableOptional
instead
of Optional
): stream.flatMap(StreamableOptional::stream)
.
Arrays.asList(StreamableOptional.of(1), StreamableOptional.empty(), StreamableOptional.of(3))
.stream()
.flatMap(StreamableOptional::stream)
.forEach(System.out::println);
// yields :
// 1
// 2
It might not be the ideal setup, however you still have two possibilities:
- try to replace usages of
Optional
withStreamableOptional
upstream of your code if you can; - else :
stream.map(StreamableOptional::ofOptional).flatMap(StreamableOptional::stream)
orstream.map(opt -> StreamableOptional.ofOptional(opt).stream())
This might indicate a code smell since:
- you probably used
StreamableOptional
to flatMap values into aStream
(and thus have no more use for aStreamableOptional
) - you can use the same operations on
StreamableOptional
as onOptional
However, this case can happen when you interact with an API that requires an Optional
. Then simply use the toOptional()
method.
public apiMethodRequiringAnOptional(Optional<Whatever> maybe);
// call it
apiMethodRequiringAnOptional(myStreamableOptional.toOptional());
The Try
class provides convenient ways to handle computations which might fail.
Unlike Either
it is right-biased,
simplifying the use of familiar higher order functions such as map, flatMap and forEach.
In proper functional languages,
Try
is a monad where as Either
is not, (but this implementation does not satisfy the monadic laws
because of how flatMap borrows inspiration from its counterpart in java.util.Optional
)
Warning: Do not use Try
on Autoclosable
resources and expect them to close (try-with-resources) - Try
does not close resources!
private void playWithTry() {
//a chain of Exception-prone operations
Try.of(Integer::valueOf, "3f")
.map(x -> x * 2)
byte[] mybytes = {0b101, 0b001};
String myString = Try.of(Float::valueOf, "3F")
.map(f -> f * 2)
.peek(f -> System.out.println("Float? : " + f))
.peekFailure(f -> System.out.println("Exception1? : " + f.getThrowable()))
.map(f -> f / 0)
.peek(f -> System.out.println("Float? : " + f))
.peekFailure(f -> System.out.println("Exception2? : " + f.getThrowable()))
.map(f -> mybytes)
.peek(b -> System.out.println("bytes? " + b))
.peekFailure(f -> System.out.println("Exception3? : " + f.getThrowable()))
.map(bytes -> new String(bytes, 0, 10, "UTF-128"))
.peek(s -> System.out.println("String? " + s))
.peekFailure(f -> System.out.println("Exception4? : " + f.getThrowable()))
.recover(s -> s, f -> f.getMessage());
//Try from a two argument Function
Try<Integer> myTry = Try.of((a, b) -> a / b, 3, 0);
//Try to Option
Optional<Integer> oInt = myTry.toOptional();
//Try to Either
Either<Throwable, Integer> integerEither = myTry.toEither();
//flatMap - a map without nested Try as the result
Integer divByZero = Try.of(Integer::new, 3)
.flatMap(i -> div(i, 0))
.recover(Function.identity(), a -> 0);
//orElse when Failure
Integer shouldEqZero = Try.of((a, b) -> a / b, 1, 0).orElse(0);
//orElseGet when Failure (the fallback Supplier is lazy)
Integer shouldEqZero = Try.of((a, b) -> a / b, 1, 0).orElseGet(() -> 0);
//forEach only runs when success
new Success<>(3)
.forEach(i -> System.out.println("a number : " + i));
//escape the Try structure and enter a regular try-catch flow
Try<Object> myTry = Try.of(() -> {
throw new IOException("floppy drive too busy");
});
try {
myTry.orElseThrow(e -> new AWTException("hah!"));
} catch (AWTException e) {
//handle and/or propagate
}
}
private Try<Integer> div(Integer a, Integer b) {
return Try.of((x,y) -> x / y, a, b);
}
//this function must end with a value
private String handleFailure(Exception e) {
e.printStackTrace();
return "I'm afraid this didnt work because of " + e.getMessage();
}