- 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();}
About
Resources
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors5
Uh oh!
There was an error while loading.Please reload this page.