This equation makes sense for some mental models, but isn't quite the same as Option. In particular, Option[T] has the nice property that you can map over its inner type in a naive fashion `forall S. (T -> S) -> (Option[T] -> Option[S])` whereas the coalescing version above needs an additional assumption that `S` is non-nullable.
forall S not null. (T -> S) -> (T? -> S?)
This can really get in the way of some kinds of generic programming.(Bonus point for Java that has introduced Option types, that can still be null)
String? :: Option
String :: Option.Some
null :: Option.NoneThis interacts terribly with generics, since it means that only one side of the generic boundary can "own" the null value at any one time. To simplify a case where this can cause issues:
class Loadable<T>(
// null until value is loaded
var valueIfLoaded: T?,
)
// returns null if user is not found
fun getUser(id: String): Loadable<User?>
Code that interprets Loadable<T> will get stuck showing a loading bar, since it has no idea about getUser using null for anything else. This doesn't even generate a warning!The "correct thing to do here would be to add a `T: Any` bound on Loadable, which prevents T from itself being a nullable type. That would notify the author of getUser that they need to box the User?, so that the cases are kept separate:
data class Box<T>(val value: T)
class Loadable<T>(
// null until value is loaded
var valueIfLoaded: T?,
)
// returns null if user is not found
fun getUser(id: String): Loadable<Box<T?>>
These are things you don't even have to consider when using a typical well-designed Option type (which, mind you, could still have a syntax sugar for T? if you wanted it).Nullable types are a feature of the type system and require language support, but the benefit is that you don't touch typical programming patterns
val foo: T? = x()
if(foo == null) { return ... }
// foo is now T, optionality gone
v.s. val foo: Option<T> = x()
// following must be wrapped and sprinkled in .map, .flatMap, .orElse
You can sort-of achieve the first with .isPresent() + .get(), but it clutters the scope, is more verbose, and is not really idiomatic.Really? I haven't used Java in a long time, but in the languages where I've used Optional types, you'd always check for presence once and then extract the value before further use.
That said, if one ever finds themselves seriously using a nested Option, it's probably time to write a new enum for that use case.