Why the hate for null?

Look, I get it: Null pointer exceptions are annoying but so is having to constantly check every object . against null. Is this the new religion after OOP and FP are being phased out? Null just means that the object is not initialized. It's useful to indicate such a state. If you just force yourself to initialize every object to some empty value then you're going to end up passing around empty strings - but wait, an empty string might intentionally be empty or it might indicate "please fill me".

Because I can already tell you what massive insights will come out soon: That null is actually not a mistake at all, it's useful. And the most ironic part about all this? These same people who hate on null are loving nullable types. Litearlly just types that *can be null but you just have to check every time before you use them*. So there is literally no difference except it forces you on a compiler level to check for null every time you use it. When I realized this I thought I was in dreamland, how can these people worship this meaningless little feature so much, crazy. It's literally the same as having null in the language except with a tiny tiny "advantage" that the compiler forces you to check. Which 99.9% of the time, you dont want to be forced because maybe you know implicitly and you dont care [right now].

Posted Sep 3, 2018, 8:32 AM UTC

344
bjzabaPikelet, Unnamed Binary DDL•

Null pointer exceptions are annoying but so is having to constantly check every object

Very few values in your program actually need to be nullable, so you only need to check those places where they are. And you should be checking those places anyway!


Edit:

Note that you might be missing the important fact that you need some way for the compiler to remember that you've done the check. You can do this by pattern matching or flow-sensitive typing, for example. But yeah, this means you don't have to keep going back to check, which would be super annoying!

It's also a shame to see the level of negativity in the other comments. It's really useful to hear perspectives like this, because it helps us see how other people might misunderstand what we are doing, and can help improve our explanations. Let's be a little friendlier please! :)

28
nlkl•

Exactly this.

Having everything nullable by default, on the other hand, is exactly what makes it necessary to constantly check and recheck for null.

In languages where ‘nullabillity’ is encoded in the type system, you only need to check things that can actually be ‘null’, and you (generally) only need to check it once.

10
gopher9Conc•

Null makes unsound type systems, and is basically a kludge for languages that cannot into algebraic types.

Null just means that the object is not initialized

What is “not initialized” semantic-wise?

If you just force yourself to initialize every object to some empty value then you're going to end up passing around empty strings

Wrong.

These same people who hate on null are loving nullable types.

Nullable types is a yet another kludge for languages without algebraic data types. But at least it's a sound kludge.

Which 99.9% of the time, you dont want to be forced because maybe you know implicitly and you dont care

C programmers “know implicitly” all the time, that's why their code is so vulnerable. Maybe it's time to recognise that people always make errors?

22
tripl3dogdareServal•

Null makes unsound type systems, and is basically a kludge for languages that cannot into algebraic types.

Nullable types is a yet another kludge for languages without algebraic data types. But at least it's a sound kludge.

Okay, don't get me wrong, I love algebraic data types, but they have absolutely nothing to do with null. Every programming language needs some way to represent "nothing", and null is one of the oldest and by far the most widely used way to do that. ADTs came much, much later.

Also, for most C-style languages, nullable types actually make more sense than ADTs, which are really more of an ML-style thing. Can ADTs do the same job? Yeah. Are they inherently better? No, not really, that's just a matter of opinion.

3
DonaldPShimoda•

Just because something is widespread doesn't mean it's better. Allowing a null value to stand in for any type's value inherently makes for a lot of problems. It reduces the soundness of your system and makes the programmer do a lot more mental work to ensure their program will not break.

Option types allow for offloading this information into the type system directly. A variable with a non-nullable declaration can never be null, so the programmer doesn't even need to worry about it. No mucking with invariants written in the comments about what a null value would mean in this context or the other one. And when a null value is genuinely useful, an option type reminds the programmer to be sure they've checked their conditions before proceeding.

I agree with you that ADTs are not inherently better. I think they can be better sometimes, but other times the option type will be sufficient.

9
tripl3dogdareServal•

I completely agree. Languages like Java that allow null anywhere with no complaint have an issue on their hands - not an insurmountable one, or one that makes the language useless, but a very tedious and annoying problem nonetheless.

Personally, though, I find option types to be a subpar solution in a procedural or object-oriented context. Marked nullability a la Kotlin is in my opinion the better way to go, as there's no wrapper around the underlying value - it's just a union type of the type and Null. There's no weird map/filter/reduce semantics to understand just to use a potentially nullable value (obviously it's best if it comes along with a safe access operator / Elvis operator / so on). For functional contexts option types are fine, but I'm still not a huge fan in general.

I think it's also important to make non-nullability the default if you're going to bother, as in my experience if you give people the option to be lazy and not mark things non-nullable, they'll take it.

2
theindigamer•

Marked nullability a la Kotlin is in my opinion the better way to go, as there's no wrapper around the underlying value

The "wrapper" thing depends on your language. In Rust (procedural/functional), there is no difference in overhead between the two solutions. Similarly in C++ (procedural/OO). Similarly in Haskell (functional) with unboxed fields.

There's no weird map/filter/reduce semantics to understand just to use a potentially nullable value

I don't want to debate your choice of the word "weird" because that is a subjective thing. However, I don't see why you need to use it -- you can use pattern matching? Or is the problem the lack of pattern matching?

It seems to me that your impression of optional types in very much colored by Java's implementation, which is far from ergonomic.

3
DonaldPShimoda•

Hmm I don't have much (any) experience with Kotlin, but I don't like the idea of reintroducing the Null | Type union. Even though it has to be done manually (so it's an improvement), I still would rather not allow the programmer the ability to introduce that mental complexity.

I like some of Swift's handling of these things. While they do allow the strange map/filter semantics for the option types, it seems preferable to use the if let and guard let syntaxes. I find these to be extremely straightforward to read while also keeping the added complexity directly in the language (instead of having the programmer maintain that in their head).

Totally agree that default nullability is a bad approach, though. Too many dragons that way.

2
FluorineWizard•

Marked nullability in Kotlin or any other language is semantically the exact same thing as an option type. The only possible difference is in the syntactic facilities offered by the language to handle the thing.

Well that and the potentially suboptimal implementation as a regular sum type with a discriminant instead of optimising the representation like Rust does.

2
fiedzia•

That null is actually not a mistake at all, it's useful.

Most of the time its a mistake to allow it. Allowing null by default is a mistake. Not allowing to mark something as "can't be null" is another one. Using null to signal error because there is no other way is language deficiency. Lack of enforcement for null checks is another. So the issue is not about "having null", its about not_having proper support for it in the language, including proper defaults.

So there is literally no difference except it forces you on a compiler level to check for null every time you use it

Lack of runtime errors makes a lot of difference.

maybe you know implicitly and you dont care [right now]

As a user of a software, I want the compiler to force you to care. Right now.

12
CyAScott•

C# 8.0 will have a compile flag were reference types cannot accept null values unless you use some unusual syntax that changes the type to a reference type that can be null.

3
0x0ddba11Strela•

The hate is not on null itself but having every reference be nullable by default and having the null check not be enforced by the compiler. Like others here have written, in many (most?) cases null isn't even a valid option. So just make it non-nullable and you can simply eliminate all those checks. In the cases where null makes sense, the compiler forces you to check. But wouldn't you have to do this check in a language like, e.g c++, anyway?

Furthermore, a bit of implicit type refinement can remove a lot of the cumbersome repeated checking:

if (obj) obj.foo();
if (obj) obj.bar();
if (obj) obj.baz();

can be written as

if (obj) { // typechecker can deduce that obj is not null within this block
    obj.foo();
    obj.bar();
    obj.baz();
}
// ...Given that obj is a local and/or foo/bar/baz are not modifying obj
10
pilotInPyjamas•

Assuming we're taking about C: I suppose the reason is that null can mean different things based on context. It could mean that a structure is uninitialised, or it could mean that an error has occurred (e.g. malloc), or it could even be a valid value that represents that the data simply isn't there (e.g. judy arrays). Null does not allow us to differentiate between those instances, in addition it can only be used for pointers.

If you already know that a pointer should exist, then you could just dereference it and skip the null check. If you're passing by value, then null isn't going to be available, so you'll have to use a wrapper type, or return value inspection. For the other cases it's a bit less black and white.

In C, dereferencing a null pointer is undefined behaviour, so if there is any chance that your value is null, then you must perform the check anyway, otherwise, your program may fail in unexpected ways, or be optimised strangely. It may not fail with a segmentation fault, or program termination, but keep running with invalid data, which is probably worse than anything else.

In something like Haskell, you can just combine a whole bunch of "nullable" types together and only do one explicit check for all of them. You only need to do this check if you're at a level where you can handle a "null pointer exception", so the work is greatly mitigated. That being said, there is no null value in Haskell anyway. (The closest would be undefined.

It's different for every language I suppose.

8
PegasusAndAcornCone language & 3D web•

FWIW, I am very fond of NULL. In dynamic languages it is a valid value that represents the absence of a value, useful (for example) as a returned value on a failed lookup.

In static programming languages, NULL can be a semantically useful value for a reference that does not yet point to an object. Unlike sum/variant types, it occupies no extra space and potentially has a performance edge.

That said, I prefer the systems programming language distinguish a nullable reference from a non-nullable reference. The latter is guaranteed to never be null, so there is no performance and code overhead using it safely. I personally prefer that the compiler ensure I use nullable references safely, either with pattern matching or (like Kotlin) a special method call syntax. I do not find this to be annoying, as I minimize my use of nullable references to where the semantic is useful (and not because of delayed initialization).

4
Uncaffeinated•

Unlike sum/variant types, it occupies no extra space and potentially has a performance edge.

Rust specifically optimizes option types to use null under the hood where possible, so there is no difference in performance.

10

It used to be a special case in the Rust compiler but now it's part of a more general optimization of using invalid values as tags (called niche-filling). The Rust ABI is unstable and there isn't really any documentation for how it works. I found a design doc for the Swift implementation of the same idea however.

6
mamcx•

Null just means that the object is not initialized

Null have many means. "Empty" "Invalid" "No exist" etc...

And you know? null <> null, null is not = null, null <> "" <> 0 <> false <>.... null is not even "invalid" or "empty". Infect every check. When a sting = "" is ok, because SOME values have a "empty" value and a "zero" value, but NOT ALL OF THEM. Null break logic left and right.

Null is SUPER BAD, Is the evil incarnate.

​

That null is actually not a mistake at all, it's useful

This "fad" is after decades of using null everywhere. Is the opposite, is clear, with TONS of evidence and MILLONS of losses and MILLONS of debugging hours lost, that null is BAD.

This is like GOTO. Is something to be put in the past. And only used in very very vey limited use cases, that are already know.

​

Literally just types that *can be null but you just have to check every time before you use them*

And the opposite with null:

Literally just types that *can't be null but you just have to check every time before you use them* and if you fail, it infect the REST of the other values.

​

If you are bothered with the checks, is because now is OBVIOS how pervasive, problematic and infectios NULLs are. Is the compiler telling you, SCREAMING, in fact: You have something wrong (if you have so many of this).

​

Is like compiler warnings: A few here and there can be ok. But if you have thousands of them? Is NOT the fault of the compiler, is the codebase in a bad bad state.

2
Lijero•

I don't agree with the OP, but...

null <> null, null is not = null

I don't know where this would be true. Some language with horrific equality operators like JavaScript?

null <> 0 <> false

This actually is the case in a lot of low-level languages like C. NULL is 0, and false is 0, and you can compare them at will.

1
mamcx•

Exactly. Null is "undefined" so it change meaning by not only languages, but also APIs, libraries and frameworks.

1
Barrucadu•

I don't really think it's fair to criticise null for being different in different languages. That applies to everything.

2
mamcx•

And different in the use of them in the same language. The problem with null is that a situation-dependant usage. And because is normally a "value/scalar" like bools and ints are, is more problematic. It invite to be used by anything.

Is like "stringly-typed" APIs, is more lazy and easy to use nulls when is not the best just because is part of the "default" types in most languages.

1
0x0ddba11Strela•

I think this is specific to SQL where null acts more like an IEEE 754 NaN.

1
htuhola•

You have an assumption that null comes out naturally. This is not true though. Null could also be a valid address.

1
Lijero•

Null could also be a valid address.

Can you give an example of anywhere where this is the case?

1
htuhola•

Webassembly and systems that have the address 0 populated such that it doesn't produce a fault when accessed.

4

I think that in some embedded systems, 0 is a valid address. However, those systems typically also have the value of NULL changed to some other address to compensate.

1
raiph•

Here's my take on the P6 take on your take on nulls.

TL;DR. I think P6 agrees with the gist of what you're saying but adds some twists.

Don't assume that anything P6 does follows conventional thinking. Do assume it's supposed to be simple and address mere mortals from kids to individual devs in a hurry to teams working on multi-million LOC codebases.

Null pointer exceptions are annoying

Shit happens. Insight helps but the only options we have are to manage its form, prepare for it, notice when shit has happened, and respond when we notice.

but so is having to constantly check every object against null.

Right. It's the wrong way to approach the fact shit happens.

P6 agrees with you so far.

Null just means that the object is not initialized.

Well, it's used for other things too. Which is one of the problems associated with "Null". It gets used to mean different things.

But let's run with this particular definition. At this point P6 "more or less" agrees with you with the "more" being that it distinguishes an uninitialized Foo from an uninitialized Bar.

It's useful to indicate such a state.

Indeed.

If you just force yourself to initialize every object to some empty value then you're going to end up passing around empty strings - but wait, an empty string might intentionally be empty or it might indicate "please fill me".

Right. Let's not do that.

All object types in P6 are tagged unions with 1 bit used for the tag. That bit tracks whether an object is a defined object (filled in) or not.

There are two generic sub-types corresponding to this tag bit.

The syntax form for writing these sub-types is called "type smilies".

:D is the "happy" or Defined sub-type. It denotes a defined value -- which also means the value has been (successfully) initialized.

:U is the "unhappy" or Undefined or Uninitialized sub-type.

P6's generic string type is called Str. Str:D denotes a defined (filled in) string and Str:U an uninitialized one. Str covers either.

null is actually not a mistake at all, it's useful.

P6 think is that the indication of "please fill me" is not a mistake but that not carrying an indication of what type an uninitialized object is, is.

These same people who hate on null are loving nullable types. Litearlly just types that can be null but you just have to check every time before you use them.

Distinguishing nullable types and non-nullable types allows both humans and the type system to mark/distinguish types to be checked from types that don't need checking. That's a useful distinction and it's useful to have the type system know about the distinction.

As explained above, every P6 object type is nullable. You can write Str to allow through an object that is either already a defined string or hopes/hoped to be one. Or Str:D if it has to be defined. (Or Foo:U for a Foo that has to be undefined. Which is an unusual requirement but sometimes makes sense.)

You don't have to check that a Str is defined before you use it. But P6 makes it trivial to check. Or, if you prefer, to allow it to automatically convert an undefined string to an empty string if you use it as a string while also warning (by default; you can stifle or, conversely, (reversibly) "fatalize" warnings/errors for a lexical scope (down to a single statement) or for a dynamic scope). Similar options arise if you wish to use it as some other type. It unifies exceptions and conventional errors too.

It probably all sounds very complicated. It's really very simple in practice. For the most part, you don't worry about types and errors when writing code at the start. P6 automatically tracks what matters and reports errors in useful ways. Once you've got the happy path (simple test data) working then you move onto elaboration including sad path handling.

99.9% of the time, you dont want to be forced because maybe you know implicitly and you dont care [right now].

Exactly.


If you don't know P6...

P6 is a modern language with a feature list that is reminiscent of Julia's but with some big pluses and minuses.

1
wavy_lines•

It's just extending static type checking to recognize null as a distinct type with such properties like: 'it cannot be dereferenced'.

1
thefirstfucker•

If you are programming in a language with NULL and fail using it properly, whos fault is that??? Thats right, its all on you.
Are people so stupid now that they cant remember their own dammed interfaces?
If you are the one designing them, you choose where you allow NULL and remember it. Thats it.

-4
boomshroom•

Most people work in teams, so no; they didn't design the code they're using. Even as a lone developer, you still rely on libraries and it's useful to know when the library can return null. When writing Kotlin code that talks to Java, I was having a minor panic over the simple fact that I wasn't sure if I could trust the Java code to not pass a null.

4
thefirstfucker•

Then its the fault of the library not documenting that properly. Its not hard. Only lazy.

0
evincarofautumn•

But why rely on documentation and human diligence when you can just represent these details with types (or other forms of static analysis)? Even experts make “lazy” mistakes all the time; it’s better to catch them early and fix them straight away than to let them cause issues later, and the computer takes care of the busywork so a human doesn’t have to.

Docs can be wrong, out of date, or misleading. Types automate away the mechanical checking, giving you the assurance that you’re following all the rules of using an interface, thereby freeing you up to focus on the more important matter of problem-solving.

3
thefirstfucker•

Sure i can understand that, but its not like the "solutions" we have are any better, they just put more layers of shit on top of it. And you STILL have to remember whats what with those abstractions, either unwrapping it or whatever fancy word they chose to represent it.
What makes my blood boil is that there are those saying its the worst abstraction in the history of programming, even going so far as saying its an abomination like Goto!
Why does every new thing have to shit all over the old thing? When people do that it just makes their argument that much weaker.
And just to make sure, i fucking love goto, i use it all the time.
So i will say it again, just because some people fail at using their tools properly doesnt mean the rest of us need training wheels too.
So in essence, forcing new shiny abstractions down peoples throat is my problem, programmers are worse than religious fanatics on that point.

1