Live data from Hacker News

Why nullable types?

medium.com

31–40 of 119 posts

Re: Why nullable types?

#31
Working with Dart now and it looks like it’s straight from the 90’s in a lot of aspects. It is not s bad as vanilla JS but when given a clean slate to design a new platform so many better choices could have been made...

Re: Why nullable types?

#32
post #12

Earlier quoted context omitted.

I generally agree with you but the one point they make in this article that really matters for Dart is it does not have pattern matching. Dart’s nominative type system makes much of standard FP much harder than you’d imagine. I know Rust is nominal too but Rust has a lot of complexity to support these things. It is very simple to add a Maybe and Either type to Dart (my code base has variants of these to deal with nul…

That's an interesting counter point, though another comment in a different post for this same article[1] pointed out that Dart is considering adding pattern matching too. Though I have trouble understanding how a nominal type system affects this? Does dart not have generics? That's all you need to support these patterns? I'm having trouble understanding how the type of type system plays a role here. Also, what's 'imp…

Yes Dart has generics which is how you would implement optional types yourself. The type of type system doesn't play a role here you are right. I was thinking of the really good pattern matching I have experienced usually being present with structural subtyping.

Dart is considering adding pattern matching (and real tuples!) and I think they will eventually get there. I just think that would have had to come first to make the case of optionals over nullable types more compelling.

I would call that example destructing in the normal sense and it is basically a form of pattern matching. To do that in an imperative language without pattern matching (i.e. what I am calling imperative destructuring), you end up with code like

  if (maybeFoo.isSome) {
    final foo = maybeFoo.asValue;
    ...
  }
when you really want

  if let Some(foo) = maybeFoo {...}

Re: Why nullable types?

#33
post #27

Good article, but there is no "choice". You need both and the article even explains why! You need union types if you have subtyping to avoid wrapping/unwrapping all the time. And you also need option/maybe types for the cases where you must not lose information at runtime. These types can implemented by union types even, by simply "tagging" (= wrapping) the runtime values with a class). A good language gives you both…

> You need union types if you have subtyping to avoid wrapping/unwrapping all the time. In my experience, the option types are always more concise. That's mostly because having option types encourages people to build out nice helper functions like these[0] and these[1]. Haskell's do notation and Rust's Try notation also save a lot of space. [0]: https://doc.rust-lang.org/std/option/enum.Option.html [1]: https://hacka…

I was actually not referring to specifically option types but generally sum types. E.g. inside a function I have a result of (union) type "Integer | ErrorA" in one branch and "Integer | ErrorB" in another branch. ErrorA and ErrorB are completely unrelated types (coming from thirdparty libraries, can't change them).

With union types I don't have to do anything and the return type for my function will be inferred as "Integer | ErrorA | ErrorB". Or if you prefer to work with a result type / either type, then it will become "Either Integer (ErrorA | ErrorB)". In most languages I'd have to make it "Either Integer (Either ErrorA ErrorB)" and also change the function code by lifting accordingly. That's verbose and many times I don't want to do that.

Re: Why nullable types?

#34

> it is entirely possible to live without null, and languages like Rust do. It seems as though there is this common misconception that Rust does not have a concept of null. Rust does have null pointers [1]! The reason many people do not see null often is because working with and dereferencing raw pointers is an unsafe operation 1: https://doc.rust-lang.org/std/ptr/fn.null.html

Of course Rust has a null pointer type. Otherwise, it wouldn’t really be a systems programming language :)

But most users would rarely if ever need to use that type. Option is how Rust deals with optional/nullable values.

Re: Why nullable types?

#35

>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 sys…

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

Re: Why nullable types?

#36
post #27

Earlier quoted context omitted.

> You need union types if you have subtyping to avoid wrapping/unwrapping all the time. In my experience, the option types are always more concise. That's mostly because having option types encourages people to build out nice helper functions like these[0] and these[1]. Haskell's do notation and Rust's Try notation also save a lot of space. [0]: https://doc.rust-lang.org/std/option/enum.Option.html [1]: https://hacka…

I was actually not referring to specifically option types but generally sum types. E.g. inside a function I have a result of (union) type "Integer | ErrorA" in one branch and "Integer | ErrorB" in another branch. ErrorA and ErrorB are completely unrelated types (coming from thirdparty libraries, can't change them). With union types I don't have to do anything and the return type for my function will be inferred as "I…

In Rust, you would implement From and From for a custom Error enum defined in your application/library. This will allow you to convert any ErrorA or ErrorB into Error.

Then, you could simply return Result from your function, and the conversion would happen for you behind the scenes.

Of course, unless you track the original error types, you will lose some information. But it’s a very clean way to handle multiple error types within your library imo.

An example of how this works: https://doc.rust-lang.org/rust-by-example/error/multiple_err...

Re: Why nullable types?

#37

>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 sys…

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 (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 bett…

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

Re: Why nullable types?

#38
post #36

Earlier quoted context omitted.

I was actually not referring to specifically option types but generally sum types. E.g. inside a function I have a result of (union) type "Integer | ErrorA" in one branch and "Integer | ErrorB" in another branch. ErrorA and ErrorB are completely unrelated types (coming from thirdparty libraries, can't change them). With union types I don't have to do anything and the return type for my function will be inferred as "I…

In Rust, you would implement From and From for a custom Error enum defined in your application/library. This will allow you to convert any ErrorA or ErrorB into Error. Then, you could simply return Result from your function, and the conversion would happen for you behind the scenes. Of course, unless you track the original error types, you will lose some information. But it’s a very clean way to handle multiple error…

> This will allow you to convert any ErrorA or ErrorB into Error.

I chose my example for cases where I want to keep track of the different error cases. But even if I don't, I still have a _lot_ of overhead for converting them.

Unfortunately Rust isn't a language that offers a solution to this problem so far. But there is an open RFC for it: https://github.com/rust-lang/rfcs/issues/294

Re: Why nullable types?

#39

>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 sys…

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.

Re: Why nullable types?

#40

> it is entirely possible to live without null, and languages like Rust do. It seems as though there is this common misconception that Rust does not have a concept of null. Rust does have null pointers [1]! The reason many people do not see null often is because working with and dereferencing raw pointers is an unsafe operation 1: https://doc.rust-lang.org/std/ptr/fn.null.html

Note that nowadays the preferred way to handle null pointers in APIs is the `Option` type. This unfortunately wasn't available in Rust 1.0.

https://doc.rust-lang.org/std/ptr/struct.NonNull.html

Post reply on HN