The agonizing death of an astronaut
The questions pile up, which is why Some(null) is allowed in Javaslang. "Isn't Option meant to make computations null-safe?" "How can that be?"
For example this code throws a NullPointerException (NPE):
// wrong use of Option
Option.of(1)
.map(i -> (String) null)
.map(String::intern);
So what is going on here? Is it a Javaslang bug? Or even worse, is the overall design of Javaslang's (and Scala's) Option broken?
Let's unfold the example a bit:
// 1) creates a Some(1)
Option<Integer> intOption = Option.of(1);
// 2) maps 1 to null and creates a Some(null)
Option<String> strOption = intOption.map(i -> null);
// 3) calls null.intern() and throws a NPE
String s = strOption.map(String::intern)
.getOrElse("");
What irritates Optional-users the most about step 2) is that a Some(null) is created instead of a None.
And I say: it is correct that map acts that way! map is the wrong answer to the problem if null may occur. Instead flatMapp that shit!
// correct use of Option
Option.of(1)
.map(i -> (String) null)
.flatMap(s -> Option.of(s).map(String::intern));
Dang! It works.
Is Java's Optional 'better' because it does not allow to store null?
Short answer: No.
Detailed answer: Optional types are nothing new, Optional has many names:
- Maybe (Adga, Haskell, Idris)
- Option (Coq, Javaslang, OCaml, Scala, Rust, StandardML)
- Optional (C++, Java)
- T? (Kotlin)
- Nullable (Julia)
Source: Wikipedia: "Option type".
In the following I will call it Option.
The key to understand the behavior of Option is to read the specification. Option is a so-called "Monad". "Category theorists invented monads in the 1960's.", see "Comprehending Monads" (Philip Wadler, 1990).
There are two functions that operate on Monads, map and flatMap (sometimes also called bind). These obey specific laws. I do not want to go into algebraic details here. It is sufficient to say that map has to preserve the computational context of a value. For context changes flatMap is used.
The constructor Option.of(value) puts a value into a computational context. If the value is null, the context is None, otherwise the context is Some. Preserving this context means:
Some(value).map(v -> otherValue) == Some(otherValue)
None().map(v -> otherValue) == None()
Let's fly to Mars
Imagine our value is a human and our context is a planet, either Earth or Mars. If the human wears a space suit he will be put on Mars otherwise he will be put on Earth.
Now let's assume the human lives on earth. He puts on a space suit because he is employed at NASA and needs it there. After work he wants to go swimming and puts on his swimwear.
☠ R.I.P. ☠
Poor human, now he's dead because someone silently put him on Mars as he put on his space suit.
*) He did not die because he put on his swimwear, he can't do that on Mars. He slowly starved to death there... alone... nobody knew...
We do not expect context changes when mapping values. It would break the Monad laws and cause the agonizing death of an astronaut.
There are already an increasing amount of articles why Java's Optional is broken. I recommend to read "Why java.util.Optional is broken" by Jed Wesley-Smith and "How Optional Breaks the Monad Laws and Why It Matters" by Marcello La Rocca.
Have fun!
- Daniel