Live data from Hacker News

Why nullable types?

medium.com

81–90 of 119 posts

Re: Why nullable types?

#81

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

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 struct, so you can do a lot of generalizing like you can with Option.

Re: Why nullable types?

#82
post #45
post #24

I agree that union-typed null was the best solution for Dart, but I think many of the article's criticisms of option types aren't good. > We can’t perform arithmetic on an Option any more than we could on a List . Why not? One sensible definition is Some(x) + Some(y) = Some(x + y) _ + _ = None() > In fact, with Dart, we’ve found that most existing code is already statically null safe This is a very good reason to use…

I agree with all your other points but > > We can’t perform arithmetic on an Option any more than we could on a List . > Why not? One sensible definition is (...) It's not the only sensible definition. It would be equally sensible to implement it as: Some(x) + Some(y) => Some(x+y) Some(x) + None => Some(x) None + Some(y) => Some(y) None + None => None

> > > We can’t perform arithmetic on an Option any more than we could on a List.

Is actually kind of a weird point to make. There are number of ways to add together List including:

    listA.zip(listB).map(([a, b]) => a + b);
Similarly adding together two optionals could be done with:

    optionalA.zip(optionalB).map(([a, b]) => a + b);
Or if you would rather:

    optionalA
      .zip(optionalB)
      .map(([a, b]) => a + b)
      .or(optionalA)
      .or(optionalB)

Re: Why nullable types?

#83

Back when I was really into FP, but didn't use functional languages, I decided that the humble list was the answer to all my problems. Operations over lists behave the same regardless of their length, and a 0 element list is not a special case. Say you want to send mail to someone's mailing addresses: addresses = user.addresses() for address in addresses { send_mail(address) } If they have no addresses, the program d…

You may have accidentally reinvented Icon:

https://en.wikipedia.org/wiki/Icon_(programming_language)#Go...

Re: Why nullable types?

#84
post #45
post #24

I agree that union-typed null was the best solution for Dart, but I think many of the article's criticisms of option types aren't good. > We can’t perform arithmetic on an Option any more than we could on a List . Why not? One sensible definition is Some(x) + Some(y) = Some(x + y) _ + _ = None() > In fact, with Dart, we’ve found that most existing code is already statically null safe This is a very good reason to use…

I agree with all your other points but > > We can’t perform arithmetic on an Option any more than we could on a List . > Why not? One sensible definition is (...) It's not the only sensible definition. It would be equally sensible to implement it as: Some(x) + Some(y) => Some(x+y) Some(x) + None => Some(x) None + Some(y) => Some(y) None + None => None

> It's not the only sensible definition

Right, which is why I said it's just one sensible definition. :)

Although I guess there's at least two sensible definitions of arithmetic on List, too.

Re: Why nullable types?

#85

Back when I was really into FP, but didn't use functional languages, I decided that the humble list was the answer to all my problems. Operations over lists behave the same regardless of their length, and a 0 element list is not a special case. Say you want to send mail to someone's mailing addresses: addresses = user.addresses() for address in addresses { send_mail(address) } If they have no addresses, the program d…

While this is true, this kind of flexibility makes the other layers harder. On the database side, if you use a relational store, it forces you to normal form 5 which few people are used to. On the front end, allowing everywhere an arbitrary number elements instead of mandatory one, means you need custom widgets everywhere. You need a way of marking the default one. Validation also gets exponentially more complicated.…

I know this is just a throwaway example, but feel just looking at the code layer this kind of elegance is comes at the cost of clarity and conveying intent, and I see that happen a lot.

The second method explicitly checking for null is great because it's telling me so many things very plainly:

1. There can be no address for a user 2. There is otherwise one address per one user 3. If there is no address we explicitly do nothing (if clause with no else)

If we do the same analysis for the first method it actually feels like it's conveying false conclusions. It's implying there's multiple addresses per user, for example.

And the fact that if a user has no address we don't do anything is still true, but the intent feels buried in the behavior of the loop.

By default I try to value writing code in a way that conveys the most intent possible over how it feels to write

For example, another language with nullable types (Kotlin) has

    user.address()?.let { it.sendMail() }
I've seen people write code like this, and it just feels so unnecessary, how much longer does it take to parse? And it still doesn't have the clarity of intent that a simple null check has.

Re: Why nullable types?

#86
post #9

I think dart made the wrong choice here, but I've never used the language personally or professionally. I give the author the benefit of the doubt but Optionals are so much more powerful than what this article covers. You can map, filter, reduce, chain, compose functions that all work with optionals, and lift functions that work on numbers (or any other type) to be functions that work on Optional but none of that is…

> I also found the Some(Some(3)) example just plain wrong. In those scenarios, typically you just use chain (aka flatMap) instead of map

I think you misunderstood. The point here was that you can represent different things using nested Options:

1. Some(Some(3)) = I checked it and the value is 3

2. Some(None) = I checked it and there is no value

3. None = I haven't checked it

It's very useful when building a cache, and also for things like JSON parsing (property was not present, property present but null, property present and has non-null value).

You just can't represent that using Dart's nullable types system, because "int?" and "int??" are the same, indistinguishable type.

Anyway...

One thing not mentioned in the article is that the way they've implemented it in dart is via "magic syntax", which I consider to be a big negative. Option is something anyone could implement without language support (assuming the type system already supports it). Anyone could build their own Option type and use it. (Sure, it's best to have this sort of thing in the stdlib so the stdlib will use it itself.) Nullable types with the "?" syntax makes the language itself bigger, and is something a regular user of the language couldn't implement if they wanted to.

(Yes, I'm aware that the "?" suffix is just a shortcut for the more generalized union type syntax, but I think my argument holds.)

The use of these types in regular code also requires what I consider a sort of special unintuitive requirement on how you structure your code. I have to do explicit null checks and then the compiler/interpreter just "knows" that code in certain places is null-safe through flow analysis. That kind of magic really turns me off.

I also don't buy the argument that Option types are a pain to use if your language doesn't have pattern matching. I've been doing a bit of Java lately and have banned null from my code, instead using Option from the excellent Vavr library (I find java.util.Optional to be lacking, ditto for Java's built-in collections library). I tend not to use Vavr's pattern matching, as I find it clunky. But I also have no trouble using Vavr's Option.

I guess I just prefer to use languages that have strong type systems where I can express constraints and safety through the types themselves, and not have to rely on language/compiler features to implement those constraints. I think the latter also gives you less flexibility as well.

Re: Why nullable types?

#87
post #12
post #9

I think dart made the wrong choice here, but I've never used the language personally or professionally. I give the author the benefit of the doubt but Optionals are so much more powerful than what this article covers. You can map, filter, reduce, chain, compose functions that all work with optionals, and lift functions that work on numbers (or any other type) to be functions that work on Optional but none of that is…

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…

I don't buy the pattern matching argument. I've been using Vavr's Option type in Java a lot lately (having banned the use of null from my code), and I don't have any issues with it. I avoid Vavr's pattern matching because I find the syntax to be clunky (not a criticism of the Vavr developers; I think what they've done is impressive within Java's syntax limitations). I do miss pattern matching in general, but I don't find using Option to be any more difficult without it. And no, I never use Option.get(); I have an ArchUnit test set up that will fail the build if anyone tries.

On the flip side, I do find Option a pain to use in Rust sometimes, which does have pattern matching. I think that's a function of the borrow checker making some things harder to express. So perhaps it's not Dart's missing pattern matching that would make Option hard to use there, but some other feature (or missing feature) of the language or type system?

Re: Why nullable types?

#88
post #24

I agree that union-typed null was the best solution for Dart, but I think many of the article's criticisms of option types aren't good. > We can’t perform arithmetic on an Option any more than we could on a List . Why not? One sensible definition is Some(x) + Some(y) = Some(x + y) _ + _ = None() > In fact, with Dart, we’ve found that most existing code is already statically null safe This is a very good reason to use…

> Different people will assign different meanings to null, and it's a real headache when trying to write generic code. This is a legitimate concern, but my experience has been that it is a surprisingly rare problem in practice. We software engineers are trained to believe that any system that supports 1 of something is obligated to generalize and support N of them. But you'd be surprised how far just one sentinel val…

Yeah, it is a rare problem, but it's not that rare when writing anything generic. For example, look at all the special-casing of null (ctrl-f "null") in the Java HashMap docs.[0] All of that would immediately go away if they didn't have null.

[0]: https://docs.oracle.com/javase/8/docs/api/java/util/HashMap....

Re: Why nullable types?

#89
Wow tough crowd. I appreciate the pragmatic approach taken here, and as long as there is type safety round nulls im happy to use the language.

Nitpick: As of C# 8 they’ve mostly solved nullable types for classes too with nullable reference types (though retrofitting it made it awkward and imperfect).

Re: Why nullable types?

#90

Earlier quoted context omitted.

> You can map, filter, reduce, chain, compose functions that all work with optionals, and lift functions that work on numbers (or any other type) to be functions that work on Optional but none of that is even mentioned in this piece (likely cause then its harder to justify picking nullable types instead) Maybe I'm missing something, but I don't see how that's special to option types. Here's a set of extension methods…

By compose I quite literally mean the compose function. const add3 = val => val + 3; const multiplyBy10 = val => val * 10; const subtract5 = val => val - 5; const doABunchOfMath = compose(add3, subtract5, multiplyBy10); const ninetyEight = doABunchOfMath(10); const optionallyNinetyEight = Some(10).map(doABunchOfMath) That Dart already provides these extensions feels like they are providing an optional type but throug…

Compose can also be implemented easily:

    extension NullableExtensions on T? {
      R? map(R Function(T) transform) => this == null ? null : transform(this!);
    }
    
    V Function(V) compose(Iterable functions) =>
        functions.reduce((composedFunction, function) {
          return (V value) => composedFunction(function(value));
        });
    
    num add3(num val) => val + 3;
    num multiplyBy10(num val) => val * 10;
    num subtract5(num val) => val - 5;
    
    final doABunchOfMath = compose([add3, subtract5, multiplyBy10]);
    final optionallyDoABunchOfMath = (num? value) => value.map(doABunchOfMath);

    doABunchOfMath(10); // 98
    optionallyDoABunchOfMath(10); // 98
    optionallyDoABunchOfMath(null); // null
The nullable syntax (${Type}?) also makes it clear to readers that this is a type which may or may not contain a value. If you don't supply the trailing '?', Dart will enforce that the value must exist at compile-time and you can write your functions without worrying about nulls sneaking in where they're unwanted.

In effect, int? is almost nearly Option, except you cannot represent Option> with Dart's nullable syntax.

Check out https://nullsafety.dartpad.dev/ to play around with the possibilities!

Post reply on HN