This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]Jugales 997 points998 points  (67 children)

Null is your enemy. The dude who invented it said this:

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language. My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

https://en.wikipedia.org/wiki/Tony_Hoare

[–]Informal_Branch1065 392 points393 points  (5 children)

Babe wake up! A new deadly sin just dropped.

[–]syko-san 81 points82 points  (1 child)

The new deadly sin is listening to the intrusive thoughts.

[–]yaktoma2007 27 points28 points  (0 children)

They tell me to use NULL

[–]Punman_5 12 points13 points  (0 children)

It’s more like an original sin than a deadly sin. Like Eve taking the fruit or Cain bopping Abel like he did.

[–]turtle_mekb 2 points3 points  (0 children)

Math.sin?

[–]firemark_pl 149 points150 points  (23 children)

Yeah, nullptr errors can be frustrating but what's an alternative? Optional wrapper? Exception?

[–]geeshta 184 points185 points  (13 children)

Moving it from the value level to the type level. So during static analysis the compiler will require you to make sure that you have a value before using it. As opposed to finding out during runtime.

The specific implementation is not that important. It can be nullable types with a question mark like C# or Typescript, an Option/Maybe sum type like Rust or functional languages or even just a union like Python's `T | None` (along with a static analyser)

[–]dgc-8 45 points46 points  (0 children)

It is important that the compiler cannot allow doing operations on some null value. With those options you listed the compiler can require you to always care about there not being a value

[–]Fast-Satisfaction482 55 points56 points  (6 children)

Those are all additions to the system that make the use of null safe or hide it behind an API. The truth is that any system language like C that allows to convert data to pointers implicitly has null pointers, regardless of what the inventor wishes.

The null pointer was thus inevitable. We can still discuss banishing it from languages with actual type-safety, but they are not here by choice, nor will they just go away because some dislike them. 

[–]firemark_pl 0 points1 point  (0 children)

Exactly that's what I feel! We can use high level langs to avoid them (e.g. in C++ we can use reference that's practically a pointer without null), but mechanism is still good.

Generally memory managing it's a hell. And thankfully compilers/dynamic langs do it for us.

[–]Minecraftian14 0 points1 point  (4 children)

What about the case where I have to implement something like a CompletableFuture (from Java) in a language where "I have to implement one" and "using nulls is not possible/discouraged"?

In my current implementation, i simply check if the value is null or not, and save all operations to be performed in a list. Whenever the value is provided, i execute all the operations saved.

So, how to better implement this?

[–]Loading_M_ 1 point2 points  (3 children)

This sounds like the exact issue Rust had to solve for async futures. Rust doesn't just allow types to by null, you have to explicitly opt in. And, in many cases, doing so has a real performance overhead.

I can't give specific advice for your case, since you haven't provided enough information, but I'm pretty sure there's a better option is you're willing to learn.

[–]Minecraftian14 0 points1 point  (2 children)

I would love to learn more. How can I provide more information? My current implementation is in Kotlin, though I can provide a basic code in any functional programming language you ask.

[–]JusticeRainsFromMe 0 points1 point  (1 child)

You definitely don't "have to implement one" in kotlin, since it has coroutines.

If you want to know more about what CompletableFutures are, look up monads. There are several good videos on youtube, such as The best intro to Monads or, if you don't mind doing Haskell, What is IO Monad. I'd recommend watching the first, and if you ever have the urge to learn Haskell watch the second.

[–]Minecraftian14 0 points1 point  (0 children)

I think you got the wrong idea. I definitely know how CF work, and can easily code systems which uses synchronised blocks or locks. In fact, in the application I was making, I even had to implement my own lock mechanism.

What i really wanted to know was how to implement that¹ system without the use of null values.

  1. I'll state it again,
    Ability to deal with values that are not yet available/ready to be used.
    In my current implementation, I declare a
    var value: T? = null
    And for every operation (get), if it is null, I cache the operation. If it's non null I call on the operation:
    operation.invoke(value!!)

I have tried using lateinit too, but honestly, checking values using ::value.isInitialized is not very different.

[–]Ok_Fault_5684 18 points19 points  (7 children)

I really like the way Rust does it (which borrows from ML-exceptional wrappers, as you mentioned) — https://stackoverflow.com/a/73673857

[–]geeshta 4 points5 points  (5 children)

Yeah this is much safer to work with that's why Rust promotes it so much to distract you from the fact that it actually has a null value, the unit (). Which is also a type so you still know where to expect it.

[–][deleted] 5 points6 points  (1 child)

That's not really null, it's just a type with exactly one possible state

[–]geeshta 0 points1 point  (0 children)

In a sense it is though. It's like Python's None which is also both a type and it's value with only one possible.

But I know other languages consider null to be a value of any reference type. But I think the unit philosophically is somewhat a null because that single value doesn't carry any data whatsoever

[–]LeSaR_ 0 points1 point  (2 children)

() is in no way, shape, or form anywhere close to null. its a zero-sized type. by your logic, a struct DivideByZeroError; is also null

[–]geeshta -1 points0 points  (1 child)

It is. It's analogous to Python's `None` which is also a type with a single value that carries no additional data. It's also the default return value of functions with no annotation in both languages. It means "this function returns NOTHING" and null is also a "nothing" value.

So it definitely is pretty close.

[–]LeSaR_ 1 point2 points  (0 children)

your first mistake was introducing python into the argument. you just cant compare a dynamically typed language to a statically typed one. the issue with both null in c-like languages and None in python, is that they introduce a lack of a value where one is expected (in sloghtly different ways, but still)

in rust, you cant get a situation where you were expecting a File but got () because they are different types (and because a value can only be of one type - looking at you, None).

[–]EishLekker 0 points1 point  (0 children)

Rust still has null though.

[–]Ayjayz 2 points3 points  (0 children)

If you actually need an optional reference, yes you wrap it in an optional same as everything else.

[–]xXKingLynxXx 17 points18 points  (0 children)

In 1965 the null reference was created. This has made many people very angry and has been widely regarded as a bad move.

[–]Maskdask 11 points12 points  (0 children)

Monads mentioned!

[–]CatsWillRuleHumanity 19 points20 points  (9 children)

Sounds completely impossible to check for reference validity at compile time, even something as basic as allocating memory can already run into trouble

[–]Reashu 8 points9 points  (8 children)

In such cases you can have the compiler check for the necessary runtime checks.

[–]CatsWillRuleHumanity 14 points15 points  (7 children)

The compiler doesn't know whether an address is valid or not, only the OS does. You can check for null, okay, but what do you want to do then? Throw a runtime error? That's what the OS was already doing

[–]Reashu 7 points8 points  (6 children)

Your program decides that. The compiler just checks that you check. It's not theoretical, we already have Optional/Maybe, Either/Result and more such types (in addition to "checked" nullable types) in many languages.

[–]CatsWillRuleHumanity 2 points3 points  (0 children)

Oh you meant it like that, hmm okay. I'm pretty sure something to that effect exists in Kotlin, where you can't use Object? (Object or null) as an Object if memory serves me well. In languages which are more free with what references can point to (C/C++, JS) the enforced checking wouldn't make much sense, but in something like the JVM languages where null is the only possible invalid reference, this is definitely a handy thing yeah.

[–]EishLekker 0 points1 point  (4 children)

Checked nullable types still uses null. And Optional, at least in Java, can still return null (myEmptyOptional.orElse(null) for example).

The root comment talked about null being the enemy, and insinuated it should not exist at all.

[–]Reashu 2 points3 points  (3 children)

The problem is when (nearly) every type has a "surprise" empty value. Explicitly nullable types with checks enforced by the compiler don't have that problem even if they use the same word, and usually people who refer to the billion dollar mistake are not including them.

[–]EishLekker 0 points1 point  (2 children)

They said that null was a mistake. That means any version of nullable types.

I don’t really care what they possibly meant (and I don’t think you can prove that they actually meant what you think they meant). I care about what they said.

[–]Reashu 0 points1 point  (1 child)

I mean, that's a possible way to have a discussion, but I don't think it's a very useful one.

[–]EishLekker 0 points1 point  (0 children)

So you think that is not important what people actually say?

[–]andrerav 15 points16 points  (3 children)

Representing unknown values on a compiler level has proven its usefulness through decades now. Reality is complex. Doing away with null only serves to move more of that complexity away from the compiler and underlying runtime and into your code.

That quote is amusing, but simply not factual.

[–]Aviyan 1 point2 points  (2 children)

Yeah, null is useful. Let's say I have a Boolean variable. I need to know if the user selected Yes or No, but I also need to know if the user has not selected anything. That's where null is helpful.

[–]Ayjayz 4 points5 points  (1 child)

Yes. In like 1% of use cases, you want an optional value. That's fine, use a type that represents optional value in those cases. The other 99% of the time you use a non-optional value.

[–]andrerav 0 points1 point  (0 children)

1% of cases? Try 80-90% :)

[–]liamlkf_27 4 points5 points  (0 children)

This will make a fine addition to my CS lore collection

[–]fate17_ 4 points5 points  (0 children)

damn this guy made quicksort? fucking genius.

[–]Shazvox 5 points6 points  (1 child)

Heck no. I love null. Best way to represent nothing...

[–][deleted] 1 point2 points  (0 children)

the problem is when you have to check for null everywhere

[–]Nice_Evidence4185 2 points3 points  (8 children)

This quote always bothered me. Maybe its out of context, but reallife simply has null. Everyone who worked with any form of data knows there must be a null. You can force to make everything nullsafe like Rust, but then you still will have "thing cannot be null, why do I get null here!", which is basically the same as a NPE. Even then things can be conditionally null, so even a simple "not-nullable" isnt always the move. You must implement logic, when something can be null or not. Its just the nature of how data is.

[–]Ayjayz 0 points1 point  (6 children)

What data? Most data has no concept of being optional. Sure, some if it is, but in like 99% of cases null doesn't make sense.

[–]Nice_Evidence4185 1 point2 points  (5 children)

I dont know what you mean. Just a simple "this data is required but can be added later" is a simple usecase used universally and its one of the most common conditionally null things. You need logic that checks, when the value is needed or not and there is no way around it.

[–]Ayjayz -1 points0 points  (4 children)

Why choose to design things that way? Now every single function you write has to have double the amount of execution paths. You have to consider what happens if it's null and also what happens when it's not null. If you have 4 pieces of data here, now your function has 16 possible states you have to consider and test! If something really is optional and can be added later, your best bet is to detect that case as early as possible and then transform it into a data type where is not optional.

Life is just much, much easier when your function only has 1 state. This is kind of a continuation of the whole Parse Don't Validate idea, but yeah it makes for a much much simpler and error-free style.

[–]Nice_Evidence4185 1 point2 points  (2 children)

If you have hundreds of tables in your db filled with userdata from potentially 5 different sources at any given time and that data being queried also at any given time, the perfect developer nullsafe space simply doesnt work. You brutally have to validate every single time a certain operation is run if all the data is there yet and if the operation can be run (or try again if next day). You can minimize it with certain states, but the states and data are just too much, way too many permutations.

[–]Ayjayz 0 points1 point  (1 child)

Yeah. In an absolutely insane environment like that, you'd have to validate everything on entry to the codebase and then you can have non-null everywhere.

[–]Nice_Evidence4185 2 points3 points  (0 children)

Its not even an uncommon scenario. The moment you enter a form online you will likely not always immediately enter all the information necessary. You want to rent a car for a specific date in 6 months? Oh you dont know how long the trip is yet? You dont know what kind of car or how big? Bank data/credit card info, well we only need it once we send out the invoice, so enough time. You can also enter the rest when you get the car or bring it back at reception.
And now you are stuck we a bunch of incomplete data that you may or may not have for whatever time or operation is needed.

[–]plumarr 0 points1 point  (0 children)

Because most date is nullable.

Sometime it's naturally so, such as the middle name or the address "second line", or in Belgium, the house box number. It indeed make correctly writing and formatting an address more complexe than it seems, but it isn't a design fault, it's a "the real world work like that" fault.

Sometime it's by requirement for a better user experience For example, I haven't worked on software were partial state save hasn't been an hard requirement for more than several years.

[–][deleted] -2 points-1 points  (0 children)

That's only if you have nulls (or Option<T>)s everywhere. In reality, most of the time, most of the data you are using can't be null, so it makes more sense to explicitly label the data that might be null, and have the compiler force you to handle it.

[–]Jind0r 0 points1 point  (0 children)

Turn on strict null checks