Live data from Hacker News

Null References: The Billion Dollar Mistake

infoq.com

141–150 of 158 posts

Re: Null References: The Billion Dollar Mistake

#141
post #80

Earlier quoted context omitted.

> You directly see when and where it happens, and the fix is usally straightforward. This is not true in most dynamic languages, especially ones where I/O is not typed. You have to be extremely dilligent about verifying input. JavaScript comes to mind.

> You have to be extremely dilligent about verifying input. That's true of all languages. Null references are a problem of low effort development. Calling it a billion dollar mistake is sensationalist hand-wringing. It accidentally highlighted how carelessly most programs are written, implying that without it developers wouldn't be checking inputs as strictly, because they wouldn't need to. Yes it's another type, but…

> there hasn't been a demonstrative reason to pull it" is not the reason it's not "been pulled

Languages are hard to change and backwards compatibility is paramount. Hell, some languages support null just for interoperability (i.e. Scala) when they would have otherwise not allowed it when they were created.

Null isn't expressive and is historical baggage. At this point "billion dollar" is probably an understatement.

I wonder how many people that have spent significant time writing in languages that allow null and those that don't prefer having null?

I, for one, wouldn't willingly go back to a language that allows null.

Re: Null References: The Billion Dollar Mistake

#142

C.A.R Hoare couldn't foresee consequences 55 years ago. That's a small mistake. We should blame language designers who didn't bother to handle the problem after it's been obvious.

Lot of mainstream languages nowadays support non-nullable types, i.e. TypeScript and C# (taken from F#).

[deleted]

Re: Null References: The Billion Dollar Mistake

#143

Earlier quoted context omitted.

Everything in Python is an object. In Python, containers are objects that reference other objects. https://docs.python.org/3/reference/datamodel.html https://docs.python.org/2.0/ref/objects.html

How are name bindings different than references? >>> a=[2, 3, 1] >>> b=a >>> id(a) 139731931982216 >>> id(b) 139731931982216 >>> b [2, 3, 1] >>> b.sort() >>> del(b) >>> a [1, 2, 3] >>>

In Python, lists are container objects. Container objects reference other objects. In the first line object a references objects 2, 3 ,and 1 (and any other objects in a's object heritage).

2, 3 and 1 have id's. That's what "everything is an object" fleshes out to in Python. But they don't reference other objects because they are literals. The value of 2 is also its name.

Re: Null References: The Billion Dollar Mistake

#144

Earlier quoted context omitted.

If it really can't ever be null, then it should just be an int, not an Optional . The entire reason that it is an Optional is that it CAN be null. In this hypothetical language, not initializing an int is a compiler error, assigning null to an int is a compiler error, etc. If it's an int it literally cannot be null. What ends up happening in practice is that the null is handled close to where it's created, and the re…

There are normal cases where this can happen. For example, a map should normally return an Optional when you try to retrieve a key's association. However, there may be special cases where you know that a key is present (maybe it is a constant map, maybe you just set the value of that key etc). I do agree that these cases are much rarer than the cases where a value is either always there, or the cases where a value re…

> For example, a map should normally return an Optional when you try to retrieve a key's association. However, there may be special cases where you know that a key is present (maybe it is a constant map, maybe you just set the value of that key etc).

Both of these cases are still pretty big code smells.

1. Just don't use constant maps. Instead of doing this:

    const myConfig = new Map {
        "height": 72,
        "weight": 160,
    };

    [...]

    var height = myConfig["height"].matchOptional(
        ifNull: () => 0,
        ifValue: i => i,
    );
...use a constant structure (with an anonymous type):

    const myConfig = struct {
        height: 72,
        weight: 160
    };

    var weight = myConfig.weight;
You can verify whether height or weight are null at compile time this way[1].

2. If you just set the value of the key, instead of doing this:

    dictionary[word] = getDefinition();
    let definition = dictionary[word].matchOptional(
        ifNull: () => "",
        ifValue: d => d,
    );

...do this:

    let definition = getDefinition();
    dictionary[word] = definition;
I'm aware that I'm playing fast and loose with the syntax of our pseudo-language, but note that avoiding the optional will is terser and simpler than using the optional and eating the null case--this is true in most cases in most strongly/statically-typed languages. Not only do you learn to lean on the type system in a strongly/statically-typed language, but if the syntax is well-designed, it makes it easier to lean on the type system than to not lean on the type system.

[1] You may say, but what if I'm loading from a file? The common pattern is to load a config from a file as a map, and then load it into a struct, setting defaults, like so:

    const defaultHeight = 72;
    const defaultWeight = 160;

    JsonObject configJson = json.loadFile("config.json");

    const config = struct {
        height: configJson["height"].matchOptional(
            ifNull: defaultHeight,
            ifValue: v => v.asInt(notInt: v => throw Exception("Invalid height \"{}\" in config.".format(v)))
        ),
        weight: configJson["weight"].matchOptional(
            ifNull: defaultWeight,
            ifValue: v => v.asInt(notInt: v => throw Exception("Invalid weight \"{}\" in config.".format(v)))
     };
You eventually hit cases with user data where you can't handle it (hence the throwing exceptions) but this pattern allows you to fail early, and with descriptive error messages.

Re: Null References: The Billion Dollar Mistake

#145

Earlier quoted context omitted.

Yes, which is where types like `Optional` come in. If you make a language where null doesn't exist by default, but still provide a standard way of indicating non-presence, you get the advantage of compile-time correctness checking. Also, the compiler can still optimize the `(hasValue, value)` tuple into a possibly-0 pointer when the type of the value is a pointer. (which by the way, is exactly what Rust does, among o…

They are called Nullable types in C# and must be declared with `?` after the type. But, Nullable .HasValue check is not forced and Nullable .Value will throw a different exception instead if it is null (InvalidOperationException).

Well, Depends on which type you are referring to (Which is part of what pains me with nullable ref types, as nice as it is to have)

If it's a value type (T), ? will make it Nullable and provide the behavior described.

Reference types however can always be null, and do not have a .HasValue as exampled above. However newer versions of C# let you declare nullable references on a compiler level, but rather than HasValue/Value you still have to do the null check and instead can bypass via the new deref operator (!)

Re: Null References: The Billion Dollar Mistake

#146

Earlier quoted context omitted.

They are called Nullable types in C# and must be declared with `?` after the type. But, Nullable .HasValue check is not forced and Nullable .Value will throw a different exception instead if it is null (InvalidOperationException).

Well, Depends on which type you are referring to (Which is part of what pains me with nullable ref types, as nice as it is to have) If it's a value type (T), ? will make it Nullable and provide the behavior described. Reference types however can always be null, and do not have a .HasValue as exampled above. However newer versions of C# let you declare nullable references on a compiler level, but rather than HasValue/…

Which is what I was talking about. I haven't had the opportunity to put it to use yet (converting an existing project is a big headache, it's something to do from the start) so I didn't remember the terms, only the ability.

Re: Null References: The Billion Dollar Mistake

#147

Count me amongst those who do not think they're a mistake. You need to indicate no-data-here in some fashion. If you try to use that no-data in some fashion having your program blow up from a null reference is a feature to me--in the vast majority of cases it's better go boom than silently continue doing something wrong. In the few where that's not the case you can trap the exception and go on. The real solution is w…

No one thinks that non-existence can't be represented, and all but the most extreme proof languages have the possibility of runtime error.

Null is a mistake because it is (1) ubiquitously permitted in types and (2) non-composable.

(Point #2: This caused JavaScript to have "undefined" which is a second level of nonexistence)

The Maybe/Option pattern solves both these problems.

Nullable at least solves the first one.

When people criticize "null, the billion dollar mistake", they criticize the ubiquitous, non-composable form of null.

https://www.lucidchart.com/techblog/2015/08/31/the-worst-mis...

Re: Null References: The Billion Dollar Mistake

#148
post #131

Earlier quoted context omitted.

I don't consider 'option' types to have interesting semantic differences with nullable types. YMMV. But beyond that, the absence of nullable references (really, a valid default value for every type) is a problem for record/object/struct initialisation - you either have to provide all values at allocation time, or attempt to statically check that the object is fully initialised before any use - Java has rules to that…

The difference is that you can't accidentally use an option as a pointer without checking it first, and when your APIs specify a non-nullable pointer you can rely on the callers to have checked for null. When you're reading or writing a function that accepts a non-nullable reference, you never have to worry about whether the argument is null or not. It's easier to get right, constrains the scope of certain types of e…

While I totally agree with everything you’re saying, I think they are right about it being annoying to initialize structs/records when all fields must be defined upfront. For one, it becomes harder to incrementally build a record in generic way. And if you decide to make a bunch of fields optional, then that optionality is carried with it forever, long after it’s obvious that the data exists for that field. Those are legitimately annoying things to deal with.

To avoid that annoyance, you almost have to rethink the problem. You can’t do it the imperative way, at least not without all that pain. Instead, if you don’t yet have the data, you should simply assign the field with a function call or an expression which gets that data for you. In other words, the record initialization should be pushed to a higher level of the call graph. If you do that, then every record initialization is complete.

Other solutions are more language-specific. TypeScript has implicit structural typing, so incremental construction is pretty easy. You just can’t try to tell the compiler that it belongs to the type you’re constructing, unless it actually does include all the necessary data.

In OCaml, you can define constructor functions which take all the data as named parameters. Since function currying is part of the language, you can just partially apply that function to each new piece of data, as you incrementally accumulate it. Then you finally initialize the record when the function is fully applied.

Suffice it to say that there are plenty of solutions to this problem.

Re: Null References: The Billion Dollar Mistake

#149
post #90

Out of all possible gotchas in programming languages I still find null pointers the easiest one to discover and fix. You directly see when and where it happens, and the fix is usally straightforward. Compared to that invalid pointers (stale references) are a lot more painful, since programs might continue to work for a while. Managed languages do at least prevent those. Multithreading issues are imho the biggest pain…

You directly see where the null dereference happens. But that's not necessarily where the problem actually is, because a null pointer can flow through a lot of code before it actually gets dereferenced. So "program continues to work for a while" is also a thing with them. In a language like C, a null pointer can also become an invalid non-null pointer pretty easy with pointer arithmetics.

>a null pointer can also become an invalid non-null pointer pretty easy with pointer arithmetics.

Yeah but even then it's still easy enough to see what happened when you have a pointer to address 0x0000002F or some similar small pointer.

Re: Null References: The Billion Dollar Mistake

#150
post #94

Earlier quoted context omitted.

This is a corner case, though, and one that is itself a code smell (i.e. in well-written code, it should be very rare). Having implicit null references, and implicit null checks on dereference, optimized for rare a corner case to the detriment of safety in typical code patterns, is a bad thing. And it definitely is a significant source of errors in managed memory languages, from my experience in C# and Python. It can…

> Having implicit null references, and implicit null checks on dereference, optimized for rare a corner case to the detriment of safety in typical code patterns, is a bad thing. Yes, I completely agree. I was just trying to point out that Optional is not a 100% air-tight solution, I think the problem of handling missing values is just too general to actually have a 100% solution. Still, the perfect shouldn't be the e…

> Here I don't agree. If the code producing the null is the problem, then you would have the same problem with Optional.

The crucial difference is that most reference-typed variables wouldn't be optionals in a language where references can't be null. So in practice you get rid of a lot of problems, because the type checker catches the use of null where it's simply not a valid input. In C# and Python, because every reference is potentially nullable, you have to aggressively check at every boundary where your contract is that it's not actually null. If you ever forget, and your caller passes null, then you end up with this "how did this get there?" problem.

Conversely, with optionals, you also have to handle the null case if you're at the boundary, because past the boundary you'd just use a non-optional type to propagate that value further. With implicit nulls, the boundary is entirely in your head - the language won't do anything to help you enforce it.

Post reply on HN