- Notifications
You must be signed in to change notification settings - Fork3
finn-no/lambda-companion
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
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>
TheEither
class provides a way to enforce failure handling in the same fashion asOptional
is a way to enforcenull
values handling.
Convention dictates thatLeft
is used for failure path andRight
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 anEither
in the same way asOptional.get()
.This is made on purpose to avoid code in the style ofif(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 theLeft
side:
final Either<String, String> var = ...;final Either<Integer, String> transformed = var.left().map(Integer::valueOf);
For theRight
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() succeededfinal 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 aStream
containingOptional
-s and to be willing to only retainPresent
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 useStreamableOptional
insteadofOptional
):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 anOptional
. Then simply use thetoOptional()
method.
public apiMethodRequiringAnOptional(Optional<Whatever> maybe); // call itapiMethodRequiringAnOptional(myStreamableOptional.toOptional());
TheTry
class provides convenient ways to handle computations which might fail.
UnlikeEither
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 asEither
is not, (but this implementation does not satisfy the monadic lawsbecause of how flatMap borrows inspiration from its counterpart injava.util.Optional
)
Warning: Do not useTry
onAutoclosable
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 valueprivate String handleFailure(Exception e) { e.printStackTrace(); return "I'm afraid this didnt work because of " + e.getMessage();}