Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I question the wisdom of even having Optional<T> in a language with nulls. It would raise some eyebrows if a function in Python returned an Optional type object rather than T | None. You have to do a check either way unless you're doing some cute monad-y stuff.


Python has Optional[T] defined as T | None in the stdlib https://docs.python.org/3/library/typing.html#typing.Optiona...


Maybe this is cute monady stuff, but there isn't an equivalent to Optional<Optional<T>> with only null/None. You usually don't directly write that, but you might incidentally instantiate that type when composing generic code, or a container/function won't allow nulls.


In what context would you not want to treat Optional.of(null) and null as the same? It shouldn't be a big deal.


In any context where you're combining two things that have different kinds of absence. E.g. if you have a cache around an expensive API call, you want to be able to cache null results from that API call, so you need a distinction between "not in the cache" and "cache entry where the API returned null".


Java for example has Map.computeIfAbsent. This function returns a nullable value and does not need 2 layers of if absence to work. I would personally argue that exposing the 2 levels of absence is exposing an implementation details, but I'll accept this as a valid usage of double optionals.


computeIfAbsent doesn't need a second kind of absence because it can never return absence. That's fine if that API is a good fit for your use case, but it isn't always (there's a reason Map.get exists as well as Map.computeIfAbsent).

> I would personally argue that exposing the 2 levels of absence is exposing an implementation details

Probably you wouldn't want to return Option<Option<X>> (or Nullable) to outside callers - maybe you want to convert None to one kind of domain-meaningful error and Some(None) to a different kind of domain-meaningful error, maybe you want to take some different codepaths to respond to "recover" from the different kinds of absence.

But it's extremely valuable to be able to compose together existing libraries that might use absence to mean something and have them just do the right thing rather than always having to worry about the edge cases where one has a kind of absence that's subtly different from the other's kind of absence. I mean fundamentally you can't ever assume that a random third-party function in Java is safe to call with null, because many of them aren't. But you also can't ever assume that a random third-party function won't return null, because some of them do. So even to just compose two functions you've got to check their docs and think about the behaviour of this special value, and it's just all so avoidable.


The None branch of each level of a nested Optional has a different meaning.


But typically it boils down to either you have the data or you don't. It's a subtle difference which I argue you can live without.


Often people use optional or nullable types as a convenient approximation to an Either type.


I still don't see why it would be a problem merging then down even when used like an either. If there is no value then there is no value.


In JSON/REST API bindings, where a deserializer maps JSON to language-native object/struct type, I'll often need to know the difference between:

    {}
and

    { "foo": null }
and

    { "foo": 42 }
So I'll represent that (in e.g. Rust) as:

    struct Whatever {
        foo: Option<Option<u32>>,
    }
None means not present, Some(None) means present but null, and Some(Some(42)) means present with a value.

I'll often use this in PATCH endpoints, where not-present means to leave the current value alone, null means to unset it, and a value means to set to that value.


How about a situation where the inner Optional<T> is acquired from another system or database, and the outer Optional<Optional<T>> is a local cache of the value. If the outer Optional is null, then you need to query the other system. If the outer Optional is filled and the inner Optional is null, then you know that the other system explicitly has no value for the data item, and can skip the query. Seems like using nested optionals would be natural here, although of course alternative representations are possible.


what is the advantage of using that over 2 variables? the cost is extra mental load, what does it buy?


It works quite well in Scala, which still tolerates nulls due to being in the JVM and having Java interop. Realistically nothing in the language is going to return null, so the only time you might have to care is when you call Java classes, and all of the Java standard library comes scalaified into having no nulls. And yes, there are enough monadic behavior in the standard library to make Option and Either quite useful, instead of just sum types.

Java really suffers with optional because the language has such love for backwards compatibility that it's extremely unlikely that nulls would even be removed from the standard library in the first place. The fact that the ecosystem relies on ugly auto wiring hacks instead of mandating explicit constructors doesn't help either.


> because the language has such love for backwards compatibility

I still remember when Java 9 introduced modules. And I’m currently pulling my hair because Java 21 renamed all javax.* into jakarta.* because Javax was a trademark of Oracle, and all libs now require a “-jakartax” version for JDK 21.

But somehow I still have to deal with nulls everywhere and erased-at-runtime generics because Java loves backwards compatibility so much. The simple fact all libs released a “-jakartax” proves the entire ecosystem is fully maintained (plus CVEs means unmaintained libs aren’t allowed in production), so they could very well release a -jdk25 version with non-null types.


There's a lot of quality-of-life stuff enabled by it in Java, since the base language's equivalents to Optional.empty(), Optional.ofNullable(...).orElse(...), etc are painfully verbose by comparison.


You're far from alone, it does make it a tiny bit easier to see which functions are expected to return null, but that's about it and messing around with it always feels like wasted effort.


I think I get what you mean, but it's confusing ofc, because Optional[T] is the older type annotation in Python for T | None

But like, when it comes to unwrappable optional/result thingies, there are libs in Python to mimic that kind of behavior from Rust and functional languages.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: