Loose ends: Optional
Brian Goetz
brian.goetz at oracle.com
Fri Jun 7 12:47:35 PDT 2013
> I think we have clearly two options, and I'm fine with both of them:
> Option A: We want to add map/flatMap.
> In that case, we also need to add a way to do the conversion back and
> fore to null (orNull() and fromNullable()).
I see your point, but I don't think this necessarily follows (though it
is certainly something to discuss.)
Neither Guava, Fugue, nor j.u.Optional allow Optional to hold a null.
The fromNullable proposal is merely a convenience method; anyone can
write it as an expression (o == null ? O.empty() : O.of(o)) or as a
static method (MyFoo.ofNullable). So it is by no means *necessary*.
Now, the same can be superficially said of map/flatMap -- people can
easily write these themselves too -- but there is a key difference;
fluency. People want flatMap not because it offers functionality people
can't write for themselves, but that they can't otherwise do it fluently
at the end of a chain. But at the other end of the chain, whether the
factory method is "Optional.ofNullable" or "MyFoo.ofNullable", there is
effectively no difference, so these are different cases.
So, I would amend your statement to: if we add map/flatMap, we then have
to ask the question of whether we should also add ofNullable.
I find this method also in the category of "mostly harmless", but left
it out of the initial proposal in spirit of minimality; limiting the set
of proposed methods to those that where having them in the JDK type adds
the most value.
If the EG will only take filter/map/flatMap if we add ofNullable, I'm OK
with that. (On the other hand, if where you are going is "if we add X
we have to add Y, and then we have to add Z, and X+Y+Z is too much, so
let's do none of them", then I'm not so OK.)
> Note that the original code proposed by Brian below already does a
> null check in map().
There are two null checks. First is a check if the mapper function
itself is null -- since a null function is clearly a bug. I assume you
are talking of the other, which is the "treat null return as nothing
there" interpretation of the mapper result. There's a valid choice here
-- NPE vs interpret as empty. The latter is consistent with the other
methods we've added to Map, such as computeIfAbsent. But we can also
discuss throwing NPE here.
> Regardless the option chosen above,
> - Optional should not implement Iterable or have a forEach method. It
> makes the code weird to read.
Sam made a very compelling argument for forEach based on experience at
Twitter over lunch last week. I'll summarize, but Sam please correct me
if I mangle it. Basically: in their library development efforts, there
were a lot of "unrelated" containerish classes (including a variant of
Optional), which each had their own class-specific method names. Over
time, the method names converged to a common vocabulary (filter, map,
forEach) even though they had no common supertype, and as a result, the
perceived usability went way up.
The current ifPresent(Consumer) is something I pulled out of a hat and
the name does not connect to anything. On the other hand,
forEach(Consumer) is familiar from a lot of other classes. I prefer
forEach, on the theory of not introducing new names for new stuff. (I
already hate the forEach / forEachRemaining distinction between
Collection and Iterator, and don't want to make this problem any worse.)
The "don't implement Iterable" and "don't call it forEach" all feel like
they are motivated by "make it less useful so people won't be tempted to
use it as much." There's a (possible) value in that Optional; giving
users multiple reasonable ways to get it at means they have to distort
their code less to use Optional effectively.
> In that case, flatMap is in my opinion useless because the code that returns null will not magically
> be re-written to send an Optional as parameter.
I think you may have a misunderstanding of what flatMap does? The point
here is that you *already* have a method that returns an Optional, and
you want to avoid double-wrapping with Optional<Optional<T>>, which is
what you'd get if you did optional.map(). For example, for "find the
first element in stream B that is larger than any element in stream A":
Optional<T> aMax = a.max();
Optional<T> nextInB = aMax.flatMap(m -> b.filter(e -> e > m)
.findFirst());
(written non-fluently for explicitness, but in reality you'd likely
chain off a.max()). Without flatMap, it would be messier.
> - filter doesn't seem to pull it's own weight, for what it worth, I
> never use it on an Option in Scala.
Agreed this is probably the least useful of the bunch, but again, can't
be easily added with fluency by users, the semantics and implementation
are obvious, and its absence might look weird given few things support
map but not filter.
So, here are the decisions I see from this list:
- Given map/flatMap, must we / do we also need ofNullable and orNull?
- If the mapper returns null, should we continue to silently convert to
empty optional or throw NPE?
- Does renaming ifPresent to forEach help or hurt? What about
implementing Iterable?
More information about the lambda-libs-spec-experts
mailing list