Optionals without this feature/restriction are actually the same as a null.
if(optional_value.has_value())
if(value != NULL)
Both of the above checks are STILL required or it's an error. Optionals only propagate the null to another method call. There has to be exhaustive pattern matching for this feature to truly shine.The important part is moving `null` out of every type in the language, and into its own little box that the compiler can tell you about. It's nice if you can make it a "library" utility without holes, but it's also fine if it's not perfect, or even if you have to make it a builtin magic thing, like C# &co.
> flatMap or Map must call the has_value method to work without crashing.
The implementation details don't really matter to the caller, what matters is that they're always operating in a safe environment without being bothered.
It matters because it's not true safety. flatMap is a higher level operation, I can make >=> or >>= work for values with null but that's not true safety.
True safety as you said, is no null. But an optional that still generates a runtime error is basically isomorphic to a null. The point of getting rid of null is not null itself, but the associated runtime error that comes with it.
[1] https://www.infoq.com/presentations/Null-References-The-Bill...
If your language can’t enforce non-nullable references then you have to worry that your Optional might be null. And if your language can enforce non nullable references then you’re boxing your types for no reason because inside if x != null you could just use x unboxed instead of x.getValue(). The only thing you miss is that None<A> and None<B> are distinct types but Go solved that with a typed nil.
For example Python with mypy is so much more ergonomic.
def foo() -> Optional[int]:
return None if coinflip() else 4
x = foo()
if x is not None:
print(x + 5) # no error
y = x * 2 # error None doesn’t have a __mul__ operator.This is completely backwards, and it's much more basic than ebingdom's explanation. Although, I don't think they're necessarily wrong, but I don't think you need functors specifically or category theory in general to explain this.
When your language allows the presence of null values, then by default you are asserting that null is a member of every type in your language. This means that any value can possibly be null.
When using Optional or Maybe or $My_Language's_Name_for_a_Some_Value_or_No_Value_Type, with a language having a half-decent typechecker, for any potential place where you may have a `Nothing` or what-have-you the typechecker will enforce that you are addressing that.
In a language that doesn't provide the ability to distinguish between null values and anything else via static analysis, you are forced at runtime to explicitly check every possible place that a null value could occur (I have used dynamically-typed languages for the better part of two decades professionally; nobody does this), or you can keep the entire logical structure of your application in your head perfectly such that you know where any nulls could occur and thereby skip writing as much code (no one can do this, and anyone who believes they can is either a junior or a junior) or acknowledge that you are a human and write a bunch of tests to try to mitigate potential failures (you will still have a microservice crash and spend a day or two putting out fires when that null slips through, because something something tests don't prove absence of bugs, thank you Dijkstra).
On the other hand, with a sufficiently sophisticated type system, you are able to write programs that let you flexibly target where optional values are present and where they are not with a minimum of boilerplate, and you never have to worry about this particular--but rather _significant_ when it comes to tolerating incessant firefighting--class of errors at runtime. This is better in every way to null.
I have to ask--how is that different from an option type?
Just to contextualize my explanation, I took it as a given that we were talking about the two _safe_ ways to handle missing data: a type system which has a notion of nullable/non-nullable types (e.g., Kotlin) vs. a type system which has Option<T> and no primitive notion of nullability (e.g., Rust).
I thought it was obvious that one would want this to be tracked in the type system _somehow_ (to prevent the billion dollar mistake), and that we were just discussing different approaches to achieving that goal.
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.
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.
> 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.
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