4 ms·
> It’s worse in every way to null. No, Optional is actually better than null because it's functorial. That means it obeys some common sense laws that one might
by ebingdom 4y ago
> It’s worse in every way to null.
No, Optional is actually better than null because it's functorial. That means it obeys some common sense laws that one might intuitively expect. Instead of reciting the functor laws, I'll give you a concrete example.
Consider a `HashMap<K, V>` type with the following API:
get(key: K) -> Option<V>
So, `get` returns `None` if the key is not found in the map. That's the proper API one would expect. It forces the caller to acknowledge the possibility that the key might not be in the map.
However, if you try to do that with nulls instead of with Optional, it breaks if the value type is nullable, because nulls don't nest. For example, if your `get` method looks more like this:
get(key: K) -> V?
and you use it with a nullable valuable type like HashMap<String, Int?>, then when the `get` method returns null you have no idea if it's because the value was null or the key was not in the map. Then, every time you want to look something up in the map, you have to first check if it's in the map with a different method and then do your lookup. This is error prone, because if you forget to do the check first, your program now has a silent bug that the type checker does not detect.
You might think to yourself, "that's silly, why would I ever want to store nulls in a map?" Well, here's one of many possible use cases: suppose you are using the map as a cache, and you want to cache the fact that something doesn't exist. This is called negative caching, and it's occasionally useful.
Or maybe you're building some generic code (like a collections library) that happens to use hash maps internally. If the hash map's get method uses a nullable type instead of Optional for its result, then it's likely that your library does not work correctly for nullable types, because it's easy to accidentally assume that null indicates that the key was not found in the map. That kind of bug won't be caught by the type checker.
Nulls are bad. Optional is good.
We really need to teach category theory to programmers so people can stop making this mistake which leads to error-prone APIs and code with hidden bugs.
- Spivak 4y agoThis assumes that functional is some undisputed good and that we want to encode all errors in the type checker. Which dgmr, there are some definite benefits but it’s a trade off — in this case it’s verbosity and running into cases where you have to write extra code to prove to the type checker that valid code is valid. mymap = { “hello”: “world” } print(mymap[“hello”]) There’s no need to handle the None case because it’s impossible. But the type checker can’t figure this one out so we have to write. x = mymap[“hello”] if x.some: print(x.unwrap) Every* language with nulls solves for this. Go returns ok, Python has KeyError. They model the same problem in a different way with different trade-offs, the main one being more ergonomic for the programmer and avoiding having to do the same checks anyway and call .unwrap.unwrap.unwrap. * Java has sinned so I can’t really defend that one.
- amluto 4y agoI disagree strongly. > print(mymap[“hello”]) There are all kinds of different semantics one might want. For example: You might want the lookup function to throw an exception on a missing item because you plan to use exception handling correctly. Python and (with “at” C++) does this, but its users mostly don’t. You might “know” the lookup can’t fail, and you don’t care what happens on failure. I hope your debugging output is good when your assumption ends up wrong some day. (Again, Python. Also C++ if you think inserting the item is reasonable on error.) You might want the lookup to return a default value if the key is not present. I hope you don’t need to distinguish not-present from present-but-the-value-is-the-default. (Go) You might not care what gets returned if the key is not present because you “know” it’s present. You will not get good debugging output if you’re wrong because the eventual failure will occur later. (Go) You might “know” the value is there and you’re willing to make it explicit in the code that you’re doing this by writing “unwrap” or something similar (Rust). You might have a logging library that implements a print function that accepts an optional and does something sensible along with a lookup function that returns optional. Then you get entirely correct semantics with no boilerplate! But null can’t actually do this because null is ambiguous.
- 8note 4y ago> get(key: K) -> Option<V> This is also not clear whether the value is missing because it's not set or because it's set to nothing You might want > get(key: K) -> Option<Option<V>> ? But you can do that with nulls too, just a matter of having a box around the value
- ebingdom 4y agoNo, the type for `get` would only have a single layer of `Option`. But V is a type parameter, so you can instantiate it with another layer of Option. You don't need to (and shouldn't) modify the type signature of the `get` method. It's already generic.