2

I have a method that returns Optional<Foo>, and if it's present, I want to use the value to look up a Stream<Bar>.

Now I know I can do this:

optionalFoo.map(foo->bars(foo)).orElse(Stream.empty());

(I'm using the full form of the mapping function instead of a method reference for illustration.)

And with Java 9+ I can do this:

optionalFoo.stream().flatMap(foo->bars(foo));

But either way the extra step seems pointless. Is there an mapping method that would simply map the optional to a stream, or an empty stream if the optional is not present? Then I could do:

optionalFoo.streamMap(foo->bars(foo));

If there isn't such a method, why not? It could be added as a simple default method on Optional.

And lastly, if I have to write my own utility method, which of the above approaches is more efficient (seeing that they both provide the same result): mapping to stream or empty stream; or converting to stream and then flat-mapping to a stream? I would assume the former is more efficient, which illustrates why a utility method is needed. (We don't want to retype .orElse(Stream.empty()) all over the place.)

For those of you who need a concrete use case, assume there is a method to return the HTML <head> element from a DOM tree:

/**
 * Finds the {@code <html><head>} element in the HTML namespace.
 * @param document The document tree.
 * @return A reference to the {@code <html><head>} element if it exists in the tree.
 */
public static Optional<Element> findHtmlHeadElement(@Nonnull final Document document);

Now another method wants to return all the HTML <meta> elements if they exist. So it wants to search the children of the <head> element, but only if it exists, of course. (Note that childElementsByNameNS() is a utility method that retrieves child elements of another element as a Stream<Element>.)

/**
 * Finds the {@code <html><head><meta>} elements in an HTML document.
 * @param document The document tree.
 * @return A stream of {@code <html><head><meta>} elements if they exist in the tree.
 */
public static Stream<Element> htmlHeadMetaElements(@Nonnull final Document document) {
  return findHtmlHeadElement(document).map(headElement ->
      childElementsByNameNS(headElement, XHTML_NAMESPACE_URI_STRING, ELEMENT_META))
          .orElse(Stream.empty());
}

Why do I need the extra ugly .orElse(Stream.empty()) boilerplate? I would rather have something like this:

/**
 * Finds the {@code <html><head><meta>} elements in an HTML document.
 * @param document The document tree.
 * @return A stream of {@code <html><head><meta>} elements if they exist in the tree.
 */
public static Stream<Element> htmlHeadMetaElements(@Nonnull final Document document) {
  return findHtmlHeadElement(document).streamMap(headElement ->
      childElementsByNameNS(headElement, XHTML_NAMESPACE_URI_STRING, ELEMENT_META));
}

You might say that "you can't have special mapping methods from Optional to every other type", but Optional and Stream have a special relationship. In fact I sort of think of Optional almost as a Stream with zero or one elements. In fact that's exactly what Optional.stream() gives back. So this is really complementary to Optional.stream().

Garret Wilson
  • 18,219
  • 30
  • 144
  • 272
  • Wouldn't that also depend on the signature of `bars(foo)`? It's important for the question to have the details related to it. For e.g. how would it handle this case `Stream.of(optionalFoo) .flatMap(a -> bars(a.orElse(null)))` . Either way, I really don't think there is a need for such a `streamMap` from an optional. Might just be opinion based, but the use case isn't much clear from the current example either. – Naman Mar 30 '19 at 15:46
  • "Wouldn't that also depend on the signature of `bars(foo)`?" How so? Certainly the parameters don't matter—that's why the whole thing in wrapped in a function lambda. And the returned stream type doesn't matter—that's why we have generics with capturing and wildcards. So I really don't understand what you're saying. And saying "I don't think there is a need" without saying why doesn't really clarify much either. You see from the example that this would obviate the need for boilerplate `.orElse(Stream.empty())`, right? So are you saying that you don't see a need to remove this boilerplate? – Garret Wilson Mar 30 '19 at 16:10
  • @Naman I've added concrete examples so you can see that this does not depend on the signature of `bars(foo)`. – Garret Wilson Mar 30 '19 at 16:46
  • 3
    Your first two code examples seem perfectly reasonable to me. Considering your use case is not terribly common, and considering it’s not terribly detrimental to readability, I’m not sure I would call it “boilerplate.” – VGR Mar 30 '19 at 22:53

1 Answers1

1

As to the question: "Why don't 'they' add this method to optional?", first a side-story.

What does 'persona' mean?

You'll find many definitions in the dictionary, and absolutely none of them are the etymologically correct one, which is 'mask'. You see, persona comes from 'per sonare': The thing you speak through. A mask.

The 'author', so to speak, of the word persona thinks it means 'mask'. But languages are dynamic things whose true meaning perhaps depends more on how the word is used and less on the author's meaning.

Back to optional.

The author of optional (Brian Goetz and the rest of Project Lambda, at Oracle) wrote it as a solution for stream operation terminals which may not make sense for empty streams. What's the return value of list.stream().max(someComparator) if the list is empty? That's it. That's all that j.u.Optional was ever meant for.

Some in the community don't quite use it that way. Some treat it more like, say, scala's optional: As a 'solution' to carrying nullity info into the type system.

This is extremely ill advised. Perhaps null as a dynamic concepts has problems and a better solution exists (debatable, but beyond the scope of this question, and not the point), but knee-jerking onto Optional being that answer, in light of absolutely no research or thought, is obviously bad. We can already see this: Java has a history of being one of the most popular languages on the planet for 20 years and has built up a gigantic ecosystem of libraries as one would expect. You can't replace an API that returns X with returning Optional<X> in a backwards compatible way, which means just about every single last one of those 20 years of libraries needs to be taken out like yesterday's garbage because there's no way to transition the API.

Ordinarily, any proposal to update a language that would require just about every existing library ever written to release a breaking update would be laughably inadequate. And yet that is precisely what would be needed if the community at large adopts the notion that optional return values should be conveyed via the API in the form of returning an Optional<T>.

In other words, 'they' (Oracle, mostly, along with the rest of the JCP) are definitely not of the opinion that that's what Optional should be used for.

And yet, the community does use it. Oracle still thinks it means 'mask', half the community insists it definitely means 'mask', but the other half of the community is starting to use 'persona' to mean the notion of the character in a play.

Now what? I have various ideas on how to fix 'null' in java, though first you should discuss whether it's even a problem that needs fixing. Well beyond the scope of this question.

However, hopefully this missive on java's null and how Optional is not a replacement for it sheds some light on why Oracle or anybody else in the JCP doesn't 'just' pile some methods into j.u.Optional to make it more suitable to using it as a general mechanism to return optional values from APIs: Because they think (and I would posit, entirely correctly), that optional should not be used for this.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • 2
    Brian Goetz [seems clear](https://stackoverflow.com/a/26328555/421049) that the purpose of `Optional` is to return a result that may not be available. That's exactly how I'm using it. If the value _is_ available, I want to call a method that returns a stream. I don't want the extra boilerplate of `.orElse(Stream.empty())`. You wrote paragraphs and paragraphs about why you don't like `Optional`, which is really beside the point. – Garret Wilson Mar 30 '19 at 16:37
  • I've added concrete examples to the question. To me this seems completely in line with Brian Goetz' [stated purpose](https://stackoverflow.com/a/26328555/421049) of `Optional`. – Garret Wilson Mar 30 '19 at 16:47