(no title)
identity0 | 5 years ago
I don't get it. These are literally all exactly the same thing, all slightly varying in ergonomics and compiler support. They may differ slightly but to call them two different categories of solution is just creating a false dichotomy for yourself.
> However, the type system is a little more flexible than with option types. The type system understands that a union type is a supertype of its branches. In other words, int is a subtype of int?. That means we can pass a definitely-present-integer to something that expects a maybe-present-integer since that’s safe to do.
You can do that in Swift, despite being placed as an example of "Solution 1".
> The short answer is that, yes, it is entirely possible to live without null, and languages like Rust do.
If you can call say that Swift has "null", then you can equally say that Rust has "null"--it's called "None". The only way "None" and "null" from other languages differ is in built-in compiler support.
adrusi|5 years ago
In haskell, Either a (Either b c) is distinct from Either (Either a b) c. In typescript, a | (b | c) is identical to (a | b) | c. And further, x | x is identical to x in typescript.
The upshot is that Haskell (using Either instead of Maybe for clarity) lets you have Either (Either a ()) () as distinct from Either a (), but the equivalent typescript is (a | null) | null, which is equivalent to a | (null | null) which is further equivalent to a | null.
So nullable types are less powerful than option types. I'm not certain that they're worse, because dealing with nested option types can get confusing, and maybe making them impossible to express would encourage API design that requires less thinking, but unfortunately I think it's more likely that it would lead to API design with more footguns.
munificent|5 years ago
They are differently powerful. Option types nest, while nullable types flatten. But nullable types subtype while option types do not.
Otherwise, I think your comment is an excellent summary of the differences.
mdasen|5 years ago
The author might be wrong about Swift being an example of Solution 1 (looking at Swift's nullability syntax).
But the point is that nullable types provide a bit more power because they're not just a data-structure, but a language feature. If you look at the source of `Option` in Rust, it's an enum like `Result` or (kinda) anything you could write rather than a language feature. Rust has built-in some things like the `?` "try" operator for special enums like `Option` and `Result` to unwrap them (or error), but it isn't quite the same as the null-guards in Dart/Kotlin and you still need to wrap them for functions like `Some(myVar)` rather than being able to pass `myVar` directly for an `Option<T>` parameter.
Again, they are almost the same, but they do talk about reasons for choosing Solution 2 over Solution 1. In Rust, you pattern-match like `match myVar { Some => x(); None => y() }` which is very functional, but they wanted something that felt like traditional null checks with conditionals. They note that nullable types are basically erased at runtime - an `int?` at runtime is either something like `7` or `null`. At compile time, you've checked that you're never assigning a `null` to an `int`, but the runtime doesn't need to know anything about it because the compiler has checked everything. Rust's enums aren't just a single special case, but something that you could make more complicated. Maybe you want a `SchrodingersResult` which could have `Success`, `Err`, or `Huh`. Ultimately, something like `Option` is an algebraic data type that you can compute off of - ex. pattern-match. The runtime needs to know that it's an `Option` because you can write code for that.
It is almost the same. The question is whether you create a one-off language feature for nullability and get advantages like knowing that any `int` can be assigned to an `int?` without needing to wrap it or whether you decide to create a type that works like any other type in your system like Java's `Optional`. Both are reasonable ways to go, but they have subtle differences and the article outlines a lot of those differences.
sanderjd|5 years ago
String s = null;
But this is not legal in languages without them:
String s = None;
In those languages, in order to have a value of None, it must be of type Option<String> (or whatever the syntax is), and in order to get a value of type String, you must assert its presence.
This is a fundamental difference with many ramifications. It really isn't just "the same but with better compile support".
_28jh|5 years ago
Larrikin|5 years ago
munificent|5 years ago
dragonwriter|5 years ago
> String s = null;
Only in languages with default-nullable types, (where the non-nullable form, if it exists at all, would be something like “String s!”); in languages with explicity billable types, the above is not allowed, because you would need to explictly opt-in to nullability:
tmpfs|5 years ago
Without these higher level types runtime null pointer exceptions are very common, using `Option` or `Maybe` creates a situation where these sorts of errors are a lot less likely.
munificent|5 years ago
Languages with nullable types are equally strict:
creata|5 years ago
IAmADHDToo|5 years ago
Option types can be nested, nullable types cannot.
gwbas1c|5 years ago
It's more like newer languages make it hard to accidentally have a null reference error.
Nullable versus Option just seems like a semantics argument. The discussion makes sense when designing a language, but when choosing a language, it's more important to just look for "compiler makes it possible to enforce that a value isn't null."
BTW:
In Rust, calling unwrap() on an Option can panic. It's just that the compiler will prevent you from passing None to a function that expects a value.
C# has nullable scalars: (int? char? float?, ect.) They compile to a Nullable<T> struct, so you can do a lot of generalizing like you can with Option<T>.