Guava's Optional
pattern is great, as it helps remove the ambiguity with null. The transform
method is very helpful for creating null-safe method chains when the first part of the chain may be absent, but isn't useful when other parts of the chain are absent.
This question is related to Guava Optional type, when transformation returns another Optional, which asks essentially the same question but for a different use case which I think may not be the intended use of Optional
(handling errors).
Consider a method Optional<Book> findBook(String id)
. findBook(id).transform(Book.getName)
works as expected. If there is no book found we get an Absent<String>
, if there is a book found we get Present<String>
.
In the common case where intermediate methods may return null
/absent()
, there does not seem to be an elegant way to chain the calls. For example, assume that Book
has a method Optional<Publisher> getPublisher()
, and we would like to get all the books published by the publisher of a book. The natural syntax would seem to be findBook(id).transform(Book.getPublisher).transform(Publisher.getPublishedBooks)
, however this will fail because the transform(Publisher.getPublishedBooks)
call will actually return an Optional<Optional<Publisher>>
.
It seems fairly reasonable to have a transform()
-like method on Optional
that would accept a function which returns an Optional
. It would act exactly like the current implementation except that it simply would not wrap the result of the function in an Optional. The implementation (for Present
) might read:
public abstract <V> Optional<V> optionalTransform(Function<? super T, Optional<V>> function) {
return function.apply(reference);
}
The implementation for Absent
is unchanged from transform
:
public abstract <V> Optional<V> optionalTransform(Function<? super T, Optional<V>> function) {
checkNotNull(function);
return Optional.absent();
}
It would also be nice if there were a way to handle methods that return null
as opposed to Optional
for working with legacy objects. Such a method would be like transform
but simply call Optional.fromNullable
on the result of the function.
I'm curious if anyone else has run into this annoyance and found nice workarounds (which don't involve writing your own Optional
class). I'd also love to hear from the Guava team or be pointed to discussions related to the issue (I didn't find any in my searching).
Description. The optional chaining operator provides a way to simplify accessing values through connected objects when it's possible that a reference or function may be undefined or null .
Optional chaining is a safe and concise way to perform access checks for nested object properties.
In a nutshell, the Optional class includes methods to explicitly deal with the cases where a value is present or absent. However, the advantage compared to null references is that the Optional class forces you to think about the case when the value is not present.
Optional chaining is not a feature specific to TypeScript.
You are looking for some Monad, but Guava's Optional (as opposite to for example Scala's Option) is just a Functor.
What the hell is a Functor?!
Functor and Monad are a kind of box, a context that wraps some value. Functor containing some value of type A knows how to apply function A => B and put the result back into Functor. For example: get something out of Optional, transform, and wrap back into Optional. In functional programming languages such method is often named 'map'.
Mona.. what?
Monad is almost the same thing as Functor, except that it consumes function returning value wrapped in Monad (A => Monad, for example Int => Optional). This magic Monad's method is often called 'flatMap'.
Here you can find really awesome explanations for fundamental FP terms: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
Functors & Monads are coming!
Optional from Java 8 can be classified as both Functor (http://docs.oracle.com/javase/8/docs/api/java/util/Optional.html#map-java.util.function.Function-) and Monad (http://docs.oracle.com/javase/8/docs/api/java/util/Optional.html#flatMap-java.util.function.Function-).
Nice mon(ad)olog, Marcin, but how can I solve my particular problem?
I'm currently working on a project that uses Java 6 and yesterday I write some helper class, called 'Optionals', which saved me a lot of time.
It provides some helper method, that allows me to turn Optional into Monads (flatMap).
Here is the code: https://gist.github.com/mkubala/046ae20946411f80ac52
Because my project's codebase still uses nulls as a return value, I introduced Optionals.lift(Function), which can be used to wrapping results into the Optional.
Why lifting result into Optional? To avoid situation when function passed into transform might return null and whole expression would return "present of null" (which by the way is not possible with Guava's Optional, because of this postcondition -> see line #71 of https://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/base/Present.java?r=0823847e96b1d082e94f06327cf218e418fe2228#71).
Couple of examples
Let's assume that findEntity() returns an Optional and Entity.getDecimalField(..) may return BigDecimal or null:
Optional<BigDecimal> maybeDecimalValue = Optionals.flatMap(
findEntity(),
new Function<Entity, Optional<BigDecimal>> () {
@Override
public Optional<BigDecimal> apply(Entity input) {
return Optional.fromNullable(input.getDecimalField(..));
}
}
);
Yet another example, assuming that I already have some Function, which extracts decimal values from Entities, and may return nulls:
Function<Entity, Decimal> extractDecimal = .. // extracts decimal value or null
Optional<BigDecimal> maybeDecimalValue = Optionals.flatMap(
findEntity(),
Optionals.lift(extractDecimal)
);
And last, but not least - your use case as an example:
Optional<Publisher> maybePublisher = Optionals.flatMap(findBook(id), Optionals.lift(Book.getPublisher));
// Assuming that getPublishedBooks may return null..
Optional<List<Book>> maybePublishedBooks = Optionals.flatMap(maybePublisher, Optionals.lift(Publisher.getPublishedBooks));
// ..or simpler, in case when getPublishedBooks never returns null
Optional<List<Book>> maybePublishedBooks2 = maybePublisher.transform(Publisher.getPublishedBooks);
// as a one-liner:
Optionals.flatMap(maybePublisher, Optionals.lift(Publisher.getPublishedBooks)).transform(Publisher.getPublishedBooks);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With