11 votes

Why nullable types in Dart?

9 comments

  1. [2]
    tesseractcat
    Link
    I think I prefer the Option method, used in Rust. I think mainly because Option makes it more obvious, and adds more friction for using nullable types, whereas adding it into the type system makes...

    I think I prefer the Option method, used in Rust. I think mainly because Option makes it more obvious, and adds more friction for using nullable types, whereas adding it into the type system makes it too easy. It probably isn't quite as bad as in languages where everything is nullable, but I think the friction of Option is actually a pro, not a con, as it makes it very explicit that you're dealing with an nullable value.

    4 votes
    1. skybrian
      Link Parent
      Well, maybe, but it’s hard to add much verbosity when people aren’t used to it. At least nullable types aren’t the default anymore in Dart, and it seems it took quite a lot of work to get there....

      Well, maybe, but it’s hard to add much verbosity when people aren’t used to it. At least nullable types aren’t the default anymore in Dart, and it seems it took quite a lot of work to get there.

      Dart was originally designed to be familiar to Java and JavaScript programmers. Rust is more of a fresh start in comparison. You’re expected to have to work a bit to learn it.

      1 vote
  2. [6]
    Moonchild
    Link
    Most of the discussion surrounding this article has focused on the wrong things. The only difference between nullable and optional/maybe types is this: data Maybe a = Just a | Nothing data Maybe...

    Most of the discussion surrounding this article has focused on the wrong things. The only difference between nullable and optional/maybe types is this:

    data       Maybe a =       Just a | Nothing
    
    data Maybe Maybe a = Just (Maybe a)          | Nothing
                       = Just (Just a | Nothing) | Nothing
    

    Whereas:

    T?  = T | null
    
    T?? = (T?) | null
        = (T | null) | null
        =  T | null  | null
        =  T | null
    

    In other words, maybe types let you distinguish between just Nothing and Nothing, where nullable types do not. Aside from that, you can use identical syntax and provide most of the same stdlib affordances. It's purely a semantic distinction.

    1. [5]
      skybrian
      Link Parent
      The article does explain that. But there is a second difference, which is that a non-null type T is a subtype of the nullable type T?. This isn’t true of T and Maybe<T>. Furthermore, in Dart a...

      The article does explain that. But there is a second difference, which is that a non-null type T is a subtype of the nullable type T?. This isn’t true of T and Maybe<T>. Furthermore, in Dart a List<T> is a subtype of List<T?>.

      1 vote
      1. [4]
        Moonchild
        Link Parent
        Right; I was referring to the subsequent discussion which seems to have missed the meat of the article. This is true, but not particularly interesting. It's easy to have an implicit conversion...

        The article does explain that

        Right; I was referring to the subsequent discussion which seems to have missed the meat of the article.

        non-null type T is a subtype of the nullable type T?. This isn’t true of T and Maybe<T>. Furthermore, in Dart a List<T> is a subtype of List<T?>

        This is true, but not particularly interesting. It's easy to have an implicit conversion from x to Maybe x which papers over the difference, or at least shorthand syntax. Ditto for y.map(\i -> Just i).

        1 vote
        1. [3]
          skybrian
          Link Parent
          Mapping each element creates a new list. In functional languages this won’t matter other than for performance. In imperative languages it does, because you can modify the list. (Plus we do care...

          Mapping each element creates a new list. In functional languages this won’t matter other than for performance. In imperative languages it does, because you can modify the list. (Plus we do care about performance when the list is large or the conversion is done a lot.)

          1. [2]
            Moonchild
            Link Parent
            Assuming that T is a boxed type, none will be indicated by a null box; so it's a trivial optimization to elide the conversion entirely.

            Assuming that T is a boxed type, none will be indicated by a null box; so it's a trivial optimization to elide the conversion entirely.

            1. skybrian
              Link Parent
              In a language that allows mutation, the original list and the copy returned by the map() call should not be the same, because you don't want a mutation to the original list to change the copy, or...

              In a language that allows mutation, the original list and the copy returned by the map() call should not be the same, because you don't want a mutation to the original list to change the copy, or vice-versa. If the compiler can prove that that it doesn't matter then it could still be done, but it doesn't seem like a trivial optimization?