Readit News logoReadit News
hn_throwaway_99 · a year ago
The problem isn't really that null isn't an object, it's that the type system allows any object reference of a particular class to also be null.

After using Typescript for the past couple years, it's such a joy when you define a variable of type Foo and know it won't contain null. Granted, TS also has to deal with undefined vs null weirdness (and even more weirdness in that an object not containing a property is subtly different from that object property being undefined), but in general the support for "optional" typing works very well.

Java tried to add things like the Optional class, but without first-class language support it's just a mess and you have developers doing crazy things, like having a method return an Optional that will sometimes also return null, by design (yes I've actually seen this).

bionsystem · a year ago
I'm writing some powershell at the moment, after a few weeks of work I realized that sometimes my return types for some functions would change even though I explicitely declare them (for exemple a 'hashtable' would become a 'System Object[]' which is short for 'table full of garbage').

Turns out some ops without assignment and some printed messages would go into the pipeline (as in, to be piped in with `|`) and become part of the return value of the functions ; to make that work powershell changes the return type, appends the message or the non-assigned op to the hashtable and returns that as an object table.

To this day I'm not aware of any language doing this kind of thing, but maybe js does ? Anyway I fear I'm going to get some grey hair because of that.

thiht · a year ago
> having a method return an Optional that will sometimes also return null

Wow that's terrible. When Optional landed in Java (8?) I came to the conclusion that the point was to use it as a contract from the developer of a method: "I return an Optional in this method so I guarantee it won't ever be null". If this is not respected, there's absolutely no reason to use Optional.

hn_throwaway_99 · a year ago
Totally agree, which is why I was baffled when I saw this code. But it goes to show that if something isn't enforced at the compiler level, "clever" developers will find a way around it.
jjav · a year ago
> it's such a joy when you define a variable of type Foo and know it won't contain null

Why is that? I've never seen a good explanation of this point of view.

This all feels like just syntactic sugar to me. Sure you might not have null, you might have an empty object or any other variation. At the end of the day, if a value was expected to be available but is not available (for whatever reason), reliable code is going to have to deal with that absence. No matter how it is expressed.

doctor_phil · a year ago
The point is that every function is explicitly telling you if it can or can not return null. Your editor and typechecker will tell you if you need to handle null after calling that function.

If you have a non-null returning function that you need to change so it can return null, then your typechecker will tell you all the places where you now need to handle the new null return.

It doesn't need to be a very large codebase before this becomes a very useful tool to help when refactoring.

nextaccountic · a year ago
Seeing all this discussion reinforces the idea that no new programming language should have this "every type implicitly contains null" thing
tialaramex · a year ago
Tony Hoare calls it (null) his Billion Dollar Mistake.

https://www.infoq.com/presentations/Null-References-The-Bill...

ruszki · a year ago
Also no new languages should default to mutable variables, fields, and types, when the language supposed to be in the same field as Java, eg. large enterprise services. And for both problems, they should be consistent, because now they are mess (there is an example in the article regarding nulls in collections, and for mutability a great example is collect(Collectors.toList()) vs toList()).
eru · a year ago
Yes, sum types and generics solve this nicely.

It's a shame Java was designed before they had generics, so the core of the language can't take advantage of them.

Deleted Comment

jillesvangurp · a year ago
Kotlin makes nullability part of the typesystem. It's a good reference for what would happen if Java did this better because if you are using Java you can just switch to Kotlin and experience this in your own code base (you can mix Java and Kotlin code easily).

You can actually write extension functions for nullable types and calling them on a null value does not cause a Nullpointer exception. This also works with Java classes.

For example, there's a CharSequence?.isNullOrBlank(): Boolean function in the Kotlin standard library that allows you to implement a null or blank check on strings. There's a similar isNullOrEmpty function on Lists (and yes, this does work for nullable generic types too).

There are many good reasons to switch from Java to Kotlin. But this is probably one of the bigger ones and genuinely low hanging fruit. Made me feel stupid after years of dealing with Java NPEs routinely and writing a lot of very verbose and needlessly defensive Java code in attempts to avoid them. All that cruft goes away with a proper language. Other languages that do this too are available of course. Pick one. Any one that isn't Java. Dealing with this stuff in 2024 is stupid.

usrusr · a year ago
But null is still null, a very special non-reference signifying absence. And not some pretend-object that walks like a valid reference, swims like a valid reference, but somehow doesn't quite quack like one. The problems surfacing as an NPE don't magically go away by null-less approaches, they just bite you at different times, in different form. Sometimes sooner, which is good, but sometimes also later. Java made null worse by not taking nullability into the type system (these days nullability annotations do a tolerable job at pretending it was) and lacking scope functions that make it easy to check without the eyesore of flooding local scope with names. Kotlin is really awesome for not falling into the trap of chasing the pipe dream of a world without absence.
fyve · a year ago
CharSequence?.isNullOrBlank()

In java I can make my own static function

isNullOrBlank(String input)

Which works on nulls too, right?

It's annoying to have to reference another class - but otherwise it's not much less ergonomic.

usrusr · a year ago
You can, you can even call the kotlin version that way if it happens to be on the classpath, but it's truly horrible ergonomics if for every call you have to guess between class-ref-otherargs and ref-otherargs forms. It's one of those little cuts you grow numb to.
severino · a year ago
CharSequence is the base class that things like java.lang.String inherit. So I understood that as an instance method you can call like "foo".isNullOrBlank() (in Kotlin syntax)
m_fayer · a year ago
I think the nullable reference type system in c# is a perfectly workable compromise for languages that had nullability from the very beginning. Once a code base uses them fully, most null-related bugs are gone. And it’s far from an unwieldy bolt-on.
jeroenhd · a year ago
With a good IDE configured to break on nullability errors, @Nonnull and @Nullable can also be used to replace the C# system in practice (not the same, but close enough). Unfortunately, this requires coordination from different team members and external dependencies rather than standard language features.
DSMan195276 · a year ago
It's funny because I really dislike the C# approach. It's not truly enforced - any code with nullable disabled can call public functions with `null`, even if on your side they were marked as non-null.
klysm · a year ago
Yup, unfortunately for us there is a lot of C# code written without NRT. If all of your code utilizes NRT and you respect the warnings, you can nearly eliminate issues related to null. There are some paper cuts around generics, but generally it has been working quite well for me. Our entire code base has NRT enabled and the only place where null sneaks in is with EF Core missing includes. This is actually kinda nice because if you get a null-whatever exception, 99% of the time it's a missing include.
becquerel · a year ago
This is true, but you can fairly easily audit your solution for files/projects which have it disabled. And yeah, you need to verify the data at the boundaries of your trusted code - but that's the same situation as it's always been. Where NRT help is avoiding boilerplate null checks in the dense forest of your business logic.
sebazzz · a year ago
Yes, that is possible but you still get warnings and you should compile with warnings as errors anyway.
aloisdg · a year ago
The Nullable class is nice and I love to use it but it would be even better with an option type like in F#
alkonaut · a year ago
There isn’t any big difference between T? And Option<T> semantically. Many code bases use an Option<T> in C# to indicate a 0-or-1 object result, but refactor those to T? instead with nullable. It combines better e.g Option<Option<T>> doesn’t need to be handled manually.
Spivak · a year ago
I think Option<T> is an overrated construct when it's not a monad. The whole value is being able to write a chain of operations

    myobj.dothing(x).otherthing(y)
Without having to check the intermediate results. When it's just a tagged union you end up having to check for null everywhere anyway. It's better than "untyped" null because it can't pop up anywhere but it's not super ergonomic. I think Nullable where the Option is implicit does it better.

klysm · a year ago
One important distinction I didn't get at first is that Nullable<T> is a struct (not a class). It is only for value types. Nullable reference types are implemented via attributes.
DonHopkins · a year ago
Why stop at null, when you can have both null and undefined? Throw in unknown, and you've got a hat trick, a holy trinity of nothingness!

Of course the Rumsfeld Matrix further breaks down the three different types of unknowns.

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

>"Reports that say that something hasn't happened are always interesting to me, because as we know, there are known knowns; there are things we know we know. We also know there are known unknowns; that is to say we know there are some things we do not know. But there are also unknown unknowns—the ones we don't know we don't know. And if one looks throughout the history of our country and other free countries, it is the latter category that tends to be the difficult ones." -Donald Rumsfeld

1) Known knowns: These are the things we know that we know. They represent the clear, confirmed knowledge that can be easily communicated and utilized in decision-making.

2) Known unknowns: These are the things we know we do not know. This category acknowledges the presence of uncertainties or gaps in our knowledge that are recognized and can be specifically identified.

3) Unknown unknowns: These are the things we do not know we do not know. This category represents unforeseen challenges and surprises, indicating a deeper level of ignorance where we are unaware of our lack of knowledge.

And Microsoft COM hinges on the IUnknown interface.

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

>Speaking at a software conference in 2009, Tony Hoare apologized for inventing the null reference:

>"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 (ALGOL W). 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." -Tony Hoare

https://news.ycombinator.com/item?id=19568378

>"My favorite is always the Billion-Dollar Mistake of having null in the language. And since JavaScript has both null and undefined, it's the Two-Billion-Dollar Mistake." -Anders Hejlsberg

>"It is by far the most problematic part of language design. And it's a single value that -- ha ha ha ha -- that if only that wasn't there, imagine all the problems we wouldn't have, right? If type systems were designed that way. And some type systems are, and some type systems are getting there, but boy, trying to retrofit that on top of a type system that has null in the first place is quite an undertaking." -Anders Hejlsberg

Kuinox · a year ago
I think there is a way to migrate to an union type myType | null. Instead.
eru · a year ago
Union types are pretty ugly.

Eg they can't distinguish between one or two layers of nullable. Or between null and an optional null.

(In eg Rust syntax between `Option<Option<MyType>>` vs `Option<MyType>`. Or `()` vs `Option<()>`, where `()` would be how you spell `null` in Rust.)

Using tags is simpler and cleaner.

mbb70 · a year ago
Ruby has a `NilClass` and the best/worst part of it is the to_s is "", to_i is 0, to_f is 0.0, to_a is [], to_h is {}

It's incredibly clean and convenient until you wake up one morning and have no idea what is happening in your code

gls2ro · a year ago
I dont think there is something wrong with that once you think about what is a Null Element (or identity) in a group that is represented by a set of elements and a function:

Integer, + => 0

Float, + => 0.0

Array, add => []

Hash, merge => {}

and so on.

I think maybe we can debate the operations/functions, but they make sense. For Integer in some ways you can define almost all other operations that you commonly use based on the addition.

So while nil is an object when trying to find a representation in other group I find it logical or expected.

Also Ruby will not automatically try to coerce nil when not asked to do so

like for example 0 + nil will throw an error.

eru · a year ago
Integers support both addition and multiplication and taking maximum and minimums, and a few other semi-group operations. Do you want to define different Null elements for all of them?
cmiller1 · a year ago
Of course since it's Ruby you can just monkey patch those to_s methods to do whatever the hell you want, confounding anyone else working on your codebase.

I love using Ruby when I'm the only one who will ever have to look at or touch it.

Terr_ · a year ago
> the to_s is "", to_i is 0, to_f is 0.0, to_a is [], to_h is {}

I somehow can't help reading that as some sort of high school sports-cheer: "Gimme an S to the quote to the I to the oh to the F to the zero! Goooo Rubies!"

goatlover · a year ago
Those conversions make sense though. They all mean empty or null. It's what I would expect from a language like Ruby.
tubthumper8 · a year ago
Integer 0 means empty with respect to a particular operation (addition) but is not empty with respect to all operations (ex. multiplication)
falcor84 · a year ago
Indeed. One's greatest strength is also their greatest weakness.
nitwit005 · a year ago
It'd be fairly easy to modify Java so that the primitives, including null, behave more like objects. And certainly, Java has inched that direction.

It doesn't solve anything fundamental though. You could make null.toString() return "null", but in most cases that will just be a bug. You're missing a null check or failed to initialize something.

Joker_vD · a year ago
We already have None in e.g. Python but that merely means that "x.mathod_name()" instead of throwing a NullPointerException raises an AttributeError, because None has no method "method_name". Okay? Not really any meaningfully different.
eru · a year ago
Python isn't exactly statically typed.
recursive · a year ago
If Null is not a subtype of MyType, then you wouldn't be able to assign null to a variable decalred as MyType, without breaking the rest of the rules of Java. I don't really see how this could work, even theoretically.
Kamq · a year ago
There's no reason that something can't both an object and the bottom of the type hierarchy.

It's, technically, an instance of multiple inheritance. Java doesn't generally allow this, but there's tons of special cases in the compiler for things that you can't do yourself. For example, defining operators is done in the compiler, but you can't define operators for your own classes.

codebje · a year ago
It's the other way around: null needs to be a subtype of MyType, not a supertype: anywhere I can pass a MyType I should be able to pass a null.

You could get most of the way to the blog post's outcomes by simply adding a few methods to Object (is(Not)Null, primarily) and having null be a magic (because it's all classes and none) instance that will NPE on all but the few defined Object methods, I think, but none of that answers the burning question of _why_ that I feel the article doesn't really address.

DaiPlusPlus · a year ago
It would be a bottom-type of reference-types. This wouldn’t work for value-types like int, at least not without boxing, which would be very painful.
philipwhiuk · a year ago
I don't actually think it solves any problems. You still have to null check.
Larrikin · a year ago
Null should be valid.

Kotlin solved Java's problem by making it a compiler error if a value that can be null isn't checked and shown to be null or the actual value, eliminating an entire class of exceptions.

equalsione · a year ago
I’m not familiar enough with kotlin to comment fully but from your description the checker framework [0] appears to do the same thing in Java.

I confess I’m not fond of checker framework. I find the error messages can be obtuse but it is very effective.

0 - https://checkerframework.org/

jflwyasdf · a year ago
null would just mean the zero value instead of the absence of a value

  String foo = null;

  String bar = "";

  foo.equals(bar) --> true
This works well provided the data type has a sensible zero value like collection types

EDIT: I'm blocked from posting so I won't be responding further, thank you for discussion.

alkonaut · a year ago
A null collection and an empty collection are two different things. A nullable collection is one that has the state “no collection” semantically separate from “empty collection”.

Similarly an Option<byte> has 257 different values while a byte has 256 different values. That the byte has a good zero value doesn’t change that - the whole reason for choosing a maybe-byte is having 257 values, not 256.

tsimionescu · a year ago
That just gets us back to the problem for which Null is introduced in almost every lamguage: indicating the absence of a value. This is an important feature in every language, and null is the most popular solution to it (the only significant alternative is the Maybe monad).

To put this in more concrete terms, if this change were integrated in Java, how would you indicate the difference between a JSON document which doesn't contain a field VS one where the field is an empty string?

golergka · a year ago
In the end, you'll have a mixture of NULL and "" in your DB, and a couple of years later a piece of logic written in another language will fail spectacularly.