top | item 25341680

(no title)

identity0 | 5 years ago

>There are two main solutions: Use an option or maybe type or Use a nullable type

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.

discuss

order

adrusi|5 years ago

They're not the same. The difference is that what the article calls "nullable types" are based on commutative unions, while option types are based on non-commutative unions.

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

> So nullable types are less powerful than option types.

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

They touch on why they're different a bit, but it's kinda subtle.

    foo(int? i) {
      if (i != null) {
        print(i + 1);
      }
    }
In that block, you can see that the compiler can understand that the null check on `i` means that it's safe to use `i` as an int within. Likewise, I can call `foo(int? i)` with `var something = 1`. `something` is an `int` and all `int`s are also `int?`s (but `int?`s aren't `int`s without guarding against nulls). By contrast, if a method takes `Optional<Integer>` then you have to wrap every `Integer` you have to call that method (with something like `Optional.of(something)`).

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

No, this is valid in languages with nullable types:

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

    String s = null;
That's not possible with nullable types. The whole point of nullable types is to mark types that can be null. So this is possible:

    String? s = null;
But now it's not much different than this:

    Option<String> s = None;
My point is that these are fundamentally the same thing. The only difference is syntax, ergonomics, and compiler support.

Larrikin|5 years ago

That is legal in languages with poor null support. That statement would not be valid in Kotlin for example.

munificent|5 years ago

    String s = null;
This is a compile error in Dart now. We added nullable types in order to make all other types not nullable. So unless you explicitly opt in to nullability by putting "?" on the type, you can a type that does not permit null.

dragonwriter|5 years ago

> No, this is valid in languages with nullable types:

> 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:

  String s? = null

tmpfs|5 years ago

This comment is misleading. They may appear to be the same but they are not because in certain languages (like Rust) you are forced to handle the `Option` case when a value is `None` which guides a programmer's thinking in the direction of what to do in that situation.

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

> They may appear to be the same but they are not because in certain languages (like Rust) you are forced to handle the `Option` case when a value is `None` which guides a programmer's thinking in the direction of what to do in that situation.

Languages with nullable types are equally strict:

    int? maybeInt;
    maybeInt + 3; // <-- Compile error.

creata|5 years ago

They're not the same, and the biggest difference is explained in the Map<int, Option<String>> example: union types merge different uses of null that you usually don't want merged.

IAmADHDToo|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.

Option types can be nested, nullable types cannot.

gwbas1c|5 years ago

That's why I laugh when I see that a language "doesn't have null."

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>.