Optional != @Nullable
Jed Wesley-Smith
jed.wesleysmith at gmail.com
Sun Sep 30 01:13:07 PDT 2012
Having only recently caught up with the fact that the JDK is getting an
Option/Maybe/Optional type – as well some of the other standard FP kit – I
have been also catching up with the discussion around it and some of the
misconceptions about how such a type relates to the use of null.
Firstly, Option does not replace null. This idea is brought about due to
the fact that as null inhabits all types in Java, it is often used as a
convenient way to encode optionality. This is an unfortunately pervasive
technique (see Map.get for example) and is significantly problematic as it
is outside the type-system. A parameter or return value that is optional is
of type T, while a non-optional one is of type T. While there are various
documentation, annotation and static analysis techniques to provide some
safety to programs using this encoding – the type cannot tell you alone.
A method with the following signature on Sometype<A>:
<A, B> Optional<B> test(Mapper<A, Optional<B> m)
is orthogonal to the following signature:
<A, B> @Nullable B test(Predicate<A> p, Mapper<A, B> m)
or given the following interface: interface Partial<A, B> extends
Predicate<A>, Mapper<A, B>
<A, B> @Nullable B test(Partial<A, B> m)
Where calling the Mapper with an A that fails the predicate would throw an
exception. In other words, it encodes in the type system totality for a
partial function. As it is both a Predicate and a Mapper/Function it also
both filters and maps!
So, Option is a type-level encoding of optionality. Some people find this
idea a bit difficult due to the presence of the get() method, and it is
true that this is a dangerous and unsafe method from a type pov – x.get()
brings back partiality to the type. It is better replaced by:
interface Optional<A> {
…
void foreach(Effect e) // for side-effecting using the value
<B> Optional<B> map(Mapper<A, B> m) // map the raw value
<B> Optional<B> flatMap(Mapper<A, Optional<B>> m) // map and optionally
return
Iterator<A> iterator() // integrate with the usual for-each loops and
Collections code
}
These colletively give you all the semantic The flatMap operation is the
most important, it allows you to sequence together many operations without
ever leaving the confines of the Optional. Consider for instance:
String address(Session session) {
InetSocketAddress socketAddress =
session.getIoSession().getRemoteAddress();
if (socketAddress != null) {
InetAddress inetAddress = socketAddress.getAddress();
if (inetAddress != null) {
return inetAddress.getHostAddress();
}
}
return null
}
implemented with Optional.flatMap:
Option<String> address(Session session) {
return optional(
session.getIoSession().getRemoteAddress()
).flatMap(socketAddress -> optional(socketAddress.getAddress())
).flatMap(inetAddress -> optional(inetAddress.getHostAddress()));
}
This version has no conditional statements, only an adapter method to go
from null to None/EMPTY.
The FP communities such as ML/Haskell and Scala programmers have all used
these constructs very successfully for years, it is well worth looking to
them for some guidance and experience on them.
For further reading on why Guava's lack of flatMap is problematic see
http://benhutchison.wordpress.com/2012/06/05/a-rant-on-jdroids-and-wilful-ignorance/
For a general presentation on the topic of Option (and why a disjoint union
type would be invaluable too) see http://cofarrell.bitbucket.org/optional/
For an alternate implementation example of an Option class see
https://bitbucket.org/atlassian/fugue/src/master/src/main/java/com/atlassian/fugue/Option.java
There are many other resources available but these are quite digestible for
those with a Java background.
More information about the lambda-dev
mailing list