13 ms·
NULL is okay as long as you pretend it doesn't exist. I mean, in these languages, uninitialized variables exist at some point (fields start uninitialized in co
by SamReidHughes 11y ago
NULL is okay as long as you pretend it doesn't exist. I mean, in these languages, uninitialized variables exist at some point (fields start uninitialized in constructor bodies, etc), and that's why there's null, instead of defining them with some garbage value that has undefined behavior. But the right solution for users is to just pretend that it can't exist, and that uninitialized variables have a garbage value.
Of course, that's idealistic, most languages don't have some Option<T> type without a performance penalty, and pre-existing libraries exist. (Usually you should design around needing Option<T> too.)
The right language design decision for these languages (managed languages like Java) might have been to make uninitialized references have a garbage value that reliably throws an exception when used (i.e. null) -- you can copy the reference and pass it around, but you can't compare it for equality and any attempt to inspect its value results in an exception.
- MichaelGG 11y agoYou can pretend all you want, and sure if you force yourself to always use Option then many problems will go away. It still is a huge hole in your type checking and will still cause mistakes to occur.
- SamReidHughes 11y agoIt's not a huge hole and not such a big deal. Just don't design APIs to use null. I've done it, and I can tell, you, it works! (But to be fair, I haven't worked on database-backed software in half a decade.) When mistakes do occur they're the trivial kind where you get a stack trace and can work it out. It's wrong to call null pointers a billion dollar mistake, too -- if we didn't have null (and in the absence of generics that permit Option, as things were long ago), programmers would end up using in-band sentinel values instead. That would be a much worse mistake.
- paulddraper 11y ago(1) It's true that a sentinel value like NULL can cut cycles. That's low-level code though, and not high-level code. I'd suggest that most code is (should be high level). (2) It is true that you may have uninitialized values. There are several ways to address it. Java will not allow you to use uninitialized local variables. C/C++ leaves the values of some uninitialized variables undefined. There are several possible approaches. Treating null as a normal thing is not a good approach, though.
- Strilanc 11y ago> The right language design decision for these languages (managed languages like Java) might have been to make uninitialized references have a garbage value that reliably throws an exception when used (i.e. null) -- you can copy the reference and pass it around, but you can't compare it for equality and any attempt to inspect its value results in an exception. That's just a different kind of null. You still have the original problem: this thing is declared as a T, but sometimes it's not a T, so you have to inspect how the value is used before you can conclude whether it's a T or not. The correct solution is to structure the language so access can't occur before initialization. For example, you could place severe constraints on constructors so they must initialize all the fields and do nothing else until that's done. Think initializer lists from C++ (but stricter), or tagged unions from Haskell.
- SamReidHughes 11y agoThat is not an option. Things like arrays need to be allocated with a default initialization and then filled.
- Strilanc 11y agoYou can absolutely work around that: - Require an explicit initial value to be provided. - Or require that a collection/iterator/generator of values be provided (and fail if it's not large enough). - Or have a built-in ArrayBuilder<T> type that accumulates all the needed values before allowing you to retrieve the array. - Or make your language dependently typed, and change the signature of Array.get to take both an index and a proof that said index has been initialized.
- SamReidHughes 11y ago- Require an explicit initial value to be provided. Let's say you want an array of FileHandles. Under this proposal, you'd need to create an "empty" FileHandle value that you can use to fill arrays with and such. You'd have to create some "empty" state for every type. This is worse software engineering than having the possibility of a null pointer exception. (Also, you couldn't write generic code to implement an ArrayList<T> efficiently without some way to generically default-construct the T to fill most of the array entries, or the constructor would have to take a filler value.) - Or require that a collection/iterator/generator of values be provided (and fail if it's not large enough). This is a needlessly complicated way to use an array, and it still has the same downsides. - Or have a built-in ArrayBuilder<T> type that accumulates all the needed values before allowing you to retrieve the array. And how do you implement your own ArrayBuilder? What if you need something with a slower growth rate, or some other data structure that you could build out of an array with uninitialized elements, such as a deque? - Or make your language dependently typed, and change the signature of Array.get to take both an index and a proof that said index has been initialized. A far worse alternative. Late edit: Generally speaking I think you're completely missing the problem of null pointers. The problem is not that a class of error exists. It's that people try to use null values in surprising ways in their APIs. If you make the null value impractical as a sentinel, you remove the mismatch between what programmers expect about whether a value can be null.