Live data from Hacker News

NULL: The worst mistake of computer science? (2015)

lucidchart.com

341–350 of 377 posts

Re: NULL: The worst mistake of computer science? (2015)

#341
post #295
post #223

Earlier quoted context omitted.

Take a look at Kotlin or Typescript†. Basically, they decided to fully design the language with support for null-as-option. That means several things: * T (non-nullable) and T? (nullable) are different types. T? = T | null * Where T is expected T? is not accepted, but where T? is specified T is also accepted * T? is automatically cast to T in the places where it's asserted to be not null, e.g. within an if(x != null)…

> * There's syntax for providing a value in case of null. (x ?: fallback) in Kotlin, (x || fallback) in Typescript. Well, uh, unless T can be falsy. 0 || fallback === fallback. A proper ?? operator a la C# would be great, but they've pushed back against it because it doesn't entirely mesh well with nulls in the JS world. JS is still probably my favorite language to work in despite stuff like this. But that's one foot…

A true elvis and null-safe call operator like ?., ?? and/or ?: would be an improvement, but the idea doesn't seem to be gaining traction in the Javascript world where || is "good enough"

Re: NULL: The worst mistake of computer science? (2015)

#342
post #258
post #223

Earlier quoted context omitted.

Take a look at Kotlin or Typescript†. Basically, they decided to fully design the language with support for null-as-option. That means several things: * T (non-nullable) and T? (nullable) are different types. T? = T | null * Where T is expected T? is not accepted, but where T? is specified T is also accepted * T? is automatically cast to T in the places where it's asserted to be not null, e.g. within an if(x != null)…

That's a terrible approach because it's inherently noncompositional and thus breaks parametricity. You can't tell whether T? is the same type as T without knowing what T is, so if you use T? and handle null in your generic functions then they might suddenly misbehave when passed a null. (Previously said T?? rather than T?, thanks to corrections in replies)

This is incorrect. The type system of both languages makes sure that T does not include null, so the case where you "accidentally" handle the null of T is impossible.

More generally, with unions you always have this behavior but I've never seen this be a problem. If your function accepts T | U and you pass (T | U) | U it simplifies to T | U and in my experience the code that handles U is always the correct thing to do for U.

Re: NULL: The worst mistake of computer science? (2015)

#343
post #315

Earlier quoted context omitted.

> Accesses to unallocated global data is the type of errors that you typically hit on the first test run. Depends what conditions cause it; the hard part is being sure that every possible code path through the first stage will initialise the data, even the rare ones like cases where some things time out but not others. > I don't think type systems help all that much. Type + instead of -, and you're out of luck. Not m…

1) Pretty easy to guarantee if main looks like stage1(); stage2(); stage3(); etc. 2) Change a plus for a minus and it is still an int.

> 1) Pretty easy to guarantee if main looks like stage1(); stage2(); stage3(); etc.

You can only use the global program order once, I'd rather save it to spend on something more important. val result1 = stage1(); val result2 = stage2(result1); ... means my code dependencies reflect my data dependencies and I'll never get confused about what needs what or comes before or after what (or the compiler will catch me if I do), so I can refactor fearlessly.

> 2) Change a plus for a minus and it is still an int.

True. If you get your core business logic wrong then types won't help you with that (though FWIW I'd argue that it's worth having a distinct type for natural numbers, in which case - and + return different types). But I found that at least 80% of production bugs weren't errors in core business logic but rather "silly little things": nulls, uncaught exceptions, transposed fields... and types catch those more cheaply cheaper than any alternative I've seen.

Re: NULL: The worst mistake of computer science? (2015)

#344
post #239
post #155

Earlier quoted context omitted.

> This is exactly the kind of minutiae that GP was bemoaning. How is this “minutiae”? You should always know the possible range of a numeric variable or field when you create it, so why not just write what size it is? In Rust the main numeric types look like this: i32, u64, u8. You just pick the one you want.

I'm sorry, I misunderstood you. These typedefs as I'm used to them do address cross-platform issues. Storage classes are "minutiae" however when all you want is just a straight up number. Python gives me an Integer type when I want a whole number, or a Float when I want to represent partials. I don't really care to be honest how that gets represented in memory in this case.

A float in Python is an implementation-specific size, just like C. So I'm really confused about using it as an example here.

> Storage classes are "minutiae" however when all you want is just a straight up number.

You can have a single "straight up number" and mention the bit width in the language spec. The mere act of writing it down doesn't force coders to deal with any more minutiae than they already had to deal with.

> Yeah by way of higher-level abstractions ...

I strongly object to this. "float is at least x bits" and "float is exactly x bits" are the same level of abstraction, and almost every language, high or low level, picks one of those options.

Re: NULL: The worst mistake of computer science? (2015)

#345
post #342
post #258

Earlier quoted context omitted.

That's a terrible approach because it's inherently noncompositional and thus breaks parametricity. You can't tell whether T? is the same type as T without knowing what T is, so if you use T? and handle null in your generic functions then they might suddenly misbehave when passed a null. (Previously said T?? rather than T?, thanks to corrections in replies)

This is incorrect. The type system of both languages makes sure that T does not include null, so the case where you "accidentally" handle the null of T is impossible. More generally, with unions you always have this behavior but I've never seen this be a problem. If your function accepts T | U and you pass (T | U) | U it simplifies to T | U and in my experience the code that handles U is always the correct thing to d…

> The type system of both languages makes sure that T does not include null, so the case where you "accidentally" handle the null of T is impossible.

So does that mean you can't call generic methods with ? types? Because there's an excluded middle here: either something like String? is a first-class type, in which case you can invoke a T ... method with T=String? and then any T?s inside that method have the potential to misbehave, or you can't, in which case String? becomes an awkward second-class type.

Re: NULL: The worst mistake of computer science? (2015)

#346
post #327
post #207

Earlier quoted context omitted.

My degree's in mathematics and I share your disdain for pointer bit-twiddling. I still find SQL nulls difficult to reason about or diagnose. I'm sure there are times and places when their behaviour is what you want but most of the time they're just a big extra complication that you don't want or need.

In the tables I define everything is not null with sane defaults by default. The places I do allow null are few and far between (e.g. updated_at) and I'm struggling to think of instances I've used them as anything other than absence indicators. In fact I don't think I ever treat it as anything other than that in code either. Was the purpose of null ever to mean anything other than I have not been defined/set? All my…

> In fact I don't think I ever treat it as anything other than that in code either.

> Was the purpose of null ever to mean anything other than I have not been defined/set?

Different people understand null differently (it might mean "error", "value not in map", "invalid user input", ...) and there's never been a clear consensus. If "null" only ever has one meaning anywhere in your codebase, and any third-party libraries you use only ever use it to mean the same thing, you're probably ok. But as soon as there are multiple meanings you'll have confusion and bugs.

> Specifically, with the caching problem, provided you constrain the cache to reason about null == not set. I see no problem.

If you only have the one cache, sure. As soon as you have a two-level cache you start to have problems (you can no longer cache absent results, since you're using the same representation for absence from your outer cache). Or as soon as null shows up anywhere else.

It's the same problem as stuff that relies on evaluation order, or threadlocals: it's ok most of the time, as long as you're not combining it with something else that does the same thing. The trouble is the times when these noncompositional constructs break down are when you have complex nested code - which is precisely when you most need everything to work the way you'd expect.

Re: NULL: The worst mistake of computer science? (2015)

#347
That 'nothing' is inconvenient applies the same in mathematics with zero. Why do we have to have a number we can't divide by? What is 0 to the power of 0? It's a special case we always need to worry about. But its inclusion in the number system is not in question.

And I remember my distress using a financial package being told that my unused zero value still MUST have a currency! My pocket is empty, how can it have a currency? If a farmers field is empty - must I say what it is empty of - cows, sheep, aardvarks?

I think worrying about inconsistency here is worrying about the inconsistency of the world we live in. 'Nothing' is a mysterious thing we need to accept and respect.

Re: NULL: The worst mistake of computer science? (2015)

#348
post #188

Earlier quoted context omitted.

Proper typesafe systems wouldn't let you use C-style reinterpretation casts either. It's quite instructive to see how the low-level Rust people handle this.

So if you have memory-mapped IO, how would you write to a specific address? In C/C++, a reinterpret cast is exactly what you need there. What would you use in Rust?

There are various alternatives, but generally the approach is to know what address you need in advance and create an object for it at compile time that can be accessed type-safely.

e.g. https://zinc.rs/apidocs/ioreg/index.html

or the longer but more detailed http://blog.japaric.io/brave-new-io/ , which covers various approaches. It even points out that you can use the type system to enforce that peripherals are only accessed from multiple threads or parts of the program in a safe manner, which you can't do if you can just reinterpret-cast into anything.

Re: NULL: The worst mistake of computer science? (2015)

#349
post #62
post #50

> NULL is a value that is not a value. And that’s a problem. The problem isn't NULL, it's languages not enforcing the necessary checks for the "no data" condition. Option can still be NULL ("None" in rust), wrapping NULL in a struct doesn't provide any safety. The safety of Option wrapper types is from the other language features (like rust's "match") and a stricter compiler that forces the programmer to write the NU…

Or add exceptions like C++ did, if (foo_is_available) return foo; else throw FooNotAvailable();

Not sure why I am getting downvotes for this. The parent proposed a language feature to support an enforced code execution when a return value was not available:

    foo_t *maybe_get_foo(/*...*/) {
        if (/*foo_is_available*/) {
            return foo;
        } else {
           return NULL;
        }
    }

    foo_t *f = maybe_get_foo();
    if (!f) { /*...*/ }   // REQUIRED or compile error
    do_something(f->bar); // only allowed after NULL check
And I was pointing out that C++ exceptions do exactly that.

    foo_t *maybe_get_foo(/*...*/) {
        if (/*foo_is_available*/) {
            return foo;
        } else {
           throw FooNotAvailable();
        }
    }

    try {
        foo_t *f = maybe_get_foo();
        do_something(f->bar); // f cannot be NULL
    }
    catch (const FooNotAvailable& ex) {
        /*...*/
    }
Sorry if that wasn't the answer you wanted because C++ is not fashionable. But it is a valid option for how to avoid the use of NULL.

Re: NULL: The worst mistake of computer science? (2015)

#350
post #246
post #223

Earlier quoted context omitted.

Take a look at Kotlin or Typescript†. Basically, they decided to fully design the language with support for null-as-option. That means several things: * T (non-nullable) and T? (nullable) are different types. T? = T | null * Where T is expected T? is not accepted, but where T? is specified T is also accepted * T? is automatically cast to T in the places where it's asserted to be not null, e.g. within an if(x != null)…

These are great features, but they are really just syntactic sugar over algebraic types. It's not a more pleasant developer experience than sum (Option) types, it's a more pleasant experience with sum types. Ex: macro! unwrap(x, fallback) { match x { Some(n) => n, None => fallback }; } > Typescript goes even further and has the best enumeration support I've seen any language have. T | U is a fully valid type, and if…

> It's not a more pleasant developer experience than sum (Option) types, it's a more pleasant experience with sum types.

Having worked with both Scala, Haskel, Kotlin and Typescript I disagree, the dev experience for Kotlin/Typescript is miles ahead for optionality. It seems like a small difference vs something like do notation or for comphrehension but it really adds up.

It hard to explain until you've tried it. The most succinct way would be to say that optional code looks and feels nearly identical to non-optional code, instead of unwrap/do-notation which makes a very large syntactical difference.

Take a look at a piece of async Kotlin co-routine code v.s. Java/Scala (Completable)Future, same effect.

Post reply on HN