Null is an element of any given set. So it's natural enough. Maybe the way we human use it in implementations is wrong. But after all Null is defined by human anyway.
"...And you, as a consumer of that method, need to check every time if the return value is Null. If you don’t – you (usually) get a Null pointer exception and your program crashes."
Can't this be handled by the language itself, by default?
"Can't this be handled by the language itself, by default?" - yes - languages do that check, but it's done at runtime and if it fails - you get an exception.
Null is an element of any given set? I'm not sure I understand you there. Do you mean that e.g. "the absence of a natural number" is a member of the set of natural numbers?
But there's an important difference: 0 is still a number and can safely be added to other numbers, "" is a string which can safely be concatenated with other strings. Java null, however, does not denote a neutral value, it denotes an absence of value, a hole in the paper where a value should be.
In fact, if a type has a neutral element on the conceptual level (like 0 or the empty string or the /dev/null logger), then the null-object pattern is applicable and we can return NullObjects instead of null references, so null references are superfluous.
If the type has no conceptual neutral element, however, the null reference doesn't relate to a meaningful entity on the conceptual level, either. (Medusa cars which kill you if you try to read the license plate are an obvious exception)
Can't this be handled by the language itself, by default?
The problem with this is that nobody has come up with a sensible universal default other than crashing in some form or another. I doubt there is one. Static typing is just an attempt at failing earlier (at compile time, not runtime).
An example of non-crashing null behaviour: embedded systems without virtual memory where 0x0 is a valid memory address. Dereferencing/jumping to 0x0 makes your code carry on as normal. Hilarity ensues.
Smalltalk, in normal operation, never crashes when an unexpected nil is encountered. An exception is thrown, but this is actually just more bog-standard Smalltalk code running normally. (You can get a hard crash if you're doing something out of the ordinary, like calling out to a DLL. One "out of the ordinary" snippet that I like: "Semaphore allInstances do: [ :each | each release]")
If you had an OO language mandating a Missing version of every type you define, then you can guarantee that you will fail at compile time. This has a better granularity than requiring the programmer to catch exceptions. The compiler can tell you which methods you need to implement, and the IDE could even scaffold them for you!
I propose eliminating Null/None all together. Simply do not allow a pointer to be initialized to none, but require it to point to an object. The object should not be collected as long as the pointer is valid.
Just throw exceptions if you need to, and handle those. Semantically, exceptions and special null values are the same - they let you know something went wrong. However, I think that null values are a syntactic oddity much like a vestigial limb now that we have exceptions.
Ouch, forgot that. Well, I guess the database could use Null values internally, but they should be translated into exceptions as soon as possible - as in, throw a NullFieldException if the field is accessed. However, checking for these all the time would be just as wasteful as checking for null values.
In a statically typed OO language, why not have an implicitly created MissingObject? Whenever you define a class Foo, you automatically get MissingFoo for which you must define any methods that Foo understands.
Can't this be handled by the language itself, by default?
Of course; most sane languages require an explicit annotation to say that an object can be null. If it's not that type, there is simply no way to create an object that is null.
(Haskell is an example of a langauge like this; no objects can be null. Maybe and Either build on top of this system to provide a well-defined approximation of null.)
Okay, I realise this is probably a naïve question, but it hasn't been answered by any of the anti-null articles.
Usually, null as a return value has some kind of special meaning, like indicating a failure or other edge case. Making null go away doesn't mean that case can't happen, you just need to handle it differently. The example in the article
String com = tld("example").getOrElse("unknown");
is one of those lovely academic examples which is completely out of touch with reality. Unless you're doing nothing but displaying it to the user, the string "unknown" is just as bad as the null object in almost every way, and worse in others: there's a reasonable chance that "unknown" will one day be a valid TLD.
So the proposed solution with the Option<> generic type really is just a reminder to the programmer. There's no technical advantage. You might even see a decrease in performance if the compiler can't optimise it away.
Except we already have a reminder mechanism: checked exceptions. Remember those? Remember how popular they are?
C# actually has this concept of nullable types. It doesn't apply to class types which are always nullable, but structs (with mostly value semantics as opposed to identity semantics) can be either nullable or not - just like C/C++'s passing by value I suppose. Except they're so unwieldy to use due to the OMG IT MIGHT BE NULL paranoia (you can't do anything as ridiculous as access a member without unboxing... which will throw a NullPointerException if null - score!), you may as well redefine your struct as a class and use that. (which breaks cases where you didn't want it nullable. score 2!)
I won't even treat C++ references (&) as a realistic attempt at a non-nullable type system because safety and C++ don't go together anyway. [1] I've not used scala, so if someone has a good, realistic example of code where scala's non-null system works well, I'd love to see it.
I guess I have to add that I actually liked static typing for a long time (I've done my stereotypical 10000 hours of C++), until I noticed I was just slowing myself down and my code in dynamic languages wasn't any worse, just quicker/easier to modify. Is this a sign of programming maturity? Is static typing the "training wheel" mechanism for beginning programmers? I'm starting to think so.
I agree, the example with the tld part wasn't the best - maybe an url schema (http part) would have been much better.
"So the proposed solution with the Option<> generic type really is just a reminder to the programmer." - exactly. But a good program is a program that works, so we need to achieve that first. :)
But a good program is a program that works, so we need to achieve that first. :)
The thing is, static typing addresses this on the microscopic scale, where it's not difficult to make code work anyway - except maybe for novices, hence my "training wheels" analogy. I don't see any evidence that it helps correctness on a more macroscopic scale. In fact, it makes code more verbose, which makes it trickier to take in all at once.
EDIT: rather than just hating on static typing, I propose this:
How about making our languages more expressive? I don't think exceptions (try/catch/finally) are anywhere close to a global optimum for error/edge-case handling. Maybe there's no silver bullet, but I'm pretty confident we can do better than current exception handling. I hear Common Lisp 'conditions' are resumable, which sounds nice, but again only useful if you can reasonably substitute a value of some kind, a bit like the TLD example. What mechanism should we have if we really want our program to do something else in exceptional cases?
Regarding "It makes code more verbose": while "new JSome<LongSuperclassName>(myValue)" is certainly more verbose than necessary, I still think that the one bit of information "this method returns Foos" vs "this method returns Foos or nothing" is worth documenting. And in a statically typed language, the type system is an obvious place for such documentation.
Sure, if you're already going to go to the trouble of having type annotations for everything, adding an extra '?' (like in C#) isn't going to break the camel's back. It's the unboxing and wrangling with the returned value that make it more verbose.
As far as I can tell the only statically typed languages that stand any chance in terms of terseness are those with sophisticated type inference. In that case, "can return null" or not makes a lot of sense for auto-generated documentation or making it an error to compare the return value to null.
There is bound to be some wrangling with the returned value if the case of "something was returned" needs different treatment from "nothing was returned" in the program's design.
However, I agree that
match perform_foo() with
| Some x -> do_something_with x
| None -> do_without
looks a lot less messy than
JOption<Bar> baropt = performFoo();
if(baropt.isNonempty()) {
Bar bar = baropt.getValue();
doSomethingWith(bar);
}
else {
doWithout();
}
There's just something deeply unsatisfying with a statically typed language that requires so much legalese and boilerplate as Java while still permitting surprising runtime errors in virtually every executable line of the program. Abandoning static types is one approach to the dilemma, trying to plug hole by hole is another.
Conditions can do a lot more than substitute a value. Think of it like an exception in reverse, you can resume with different "types" that cause it to take a different path. And they might pass new data in.
Say you have a function that is basically a loop processing a list of files, it throws some kind of "file not found" error. You could sort the file out and do a resume "try again", select a different file and do resume "diff file (filename)" or do resume "skip".
I agree with the author that having an option datatype makes the intention "this method returns some Foo or nothing" clear.
However, the suggested encoding of an option type in Java blends poorly with subtyping because a JOption<MySubtype> is not a subtype of JOption<MySupertype>. For a quick optional-return scenario, it may work, but as a substitute for nulls in optional parameters, you'd have to remember the precise type of every parameter and create non-empty JOption instances in a rather verbose way.
As an alternative, you could use a tool like Findbugs and its @Nullable annotation.
23 comments
[ 2.8 ms ] story [ 54.9 ms ] thread"...And you, as a consumer of that method, need to check every time if the return value is Null. If you don’t – you (usually) get a Null pointer exception and your program crashes."
Can't this be handled by the language itself, by default?
In fact, if a type has a neutral element on the conceptual level (like 0 or the empty string or the /dev/null logger), then the null-object pattern is applicable and we can return NullObjects instead of null references, so null references are superfluous.
If the type has no conceptual neutral element, however, the null reference doesn't relate to a meaningful entity on the conceptual level, either. (Medusa cars which kill you if you try to read the license plate are an obvious exception)
The problem with this is that nobody has come up with a sensible universal default other than crashing in some form or another. I doubt there is one. Static typing is just an attempt at failing earlier (at compile time, not runtime).
An example of non-crashing null behaviour: embedded systems without virtual memory where 0x0 is a valid memory address. Dereferencing/jumping to 0x0 makes your code carry on as normal. Hilarity ensues.
If you had an OO language mandating a Missing version of every type you define, then you can guarantee that you will fail at compile time. This has a better granularity than requiring the programmer to catch exceptions. The compiler can tell you which methods you need to implement, and the IDE could even scaffold them for you!
Just throw exceptions if you need to, and handle those. Semantically, exceptions and special null values are the same - they let you know something went wrong. However, I think that null values are a syntactic oddity much like a vestigial limb now that we have exceptions.
I now see the advantages of the monad way.
Of course; most sane languages require an explicit annotation to say that an object can be null. If it's not that type, there is simply no way to create an object that is null.
(Haskell is an example of a langauge like this; no objects can be null. Maybe and Either build on top of this system to provide a well-defined approximation of null.)
Usually, null as a return value has some kind of special meaning, like indicating a failure or other edge case. Making null go away doesn't mean that case can't happen, you just need to handle it differently. The example in the article
is one of those lovely academic examples which is completely out of touch with reality. Unless you're doing nothing but displaying it to the user, the string "unknown" is just as bad as the null object in almost every way, and worse in others: there's a reasonable chance that "unknown" will one day be a valid TLD.So the proposed solution with the Option<> generic type really is just a reminder to the programmer. There's no technical advantage. You might even see a decrease in performance if the compiler can't optimise it away.
Except we already have a reminder mechanism: checked exceptions. Remember those? Remember how popular they are?
C# actually has this concept of nullable types. It doesn't apply to class types which are always nullable, but structs (with mostly value semantics as opposed to identity semantics) can be either nullable or not - just like C/C++'s passing by value I suppose. Except they're so unwieldy to use due to the OMG IT MIGHT BE NULL paranoia (you can't do anything as ridiculous as access a member without unboxing... which will throw a NullPointerException if null - score!), you may as well redefine your struct as a class and use that. (which breaks cases where you didn't want it nullable. score 2!)
I won't even treat C++ references (&) as a realistic attempt at a non-nullable type system because safety and C++ don't go together anyway. [1] I've not used scala, so if someone has a good, realistic example of code where scala's non-null system works well, I'd love to see it.
I guess I have to add that I actually liked static typing for a long time (I've done my stereotypical 10000 hours of C++), until I noticed I was just slowing myself down and my code in dynamic languages wasn't any worse, just quicker/easier to modify. Is this a sign of programming maturity? Is static typing the "training wheel" mechanism for beginning programmers? I'm starting to think so.
"So the proposed solution with the Option<> generic type really is just a reminder to the programmer." - exactly. But a good program is a program that works, so we need to achieve that first. :)
The thing is, static typing addresses this on the microscopic scale, where it's not difficult to make code work anyway - except maybe for novices, hence my "training wheels" analogy. I don't see any evidence that it helps correctness on a more macroscopic scale. In fact, it makes code more verbose, which makes it trickier to take in all at once.
EDIT: rather than just hating on static typing, I propose this:
How about making our languages more expressive? I don't think exceptions (try/catch/finally) are anywhere close to a global optimum for error/edge-case handling. Maybe there's no silver bullet, but I'm pretty confident we can do better than current exception handling. I hear Common Lisp 'conditions' are resumable, which sounds nice, but again only useful if you can reasonably substitute a value of some kind, a bit like the TLD example. What mechanism should we have if we really want our program to do something else in exceptional cases?
As far as I can tell the only statically typed languages that stand any chance in terms of terseness are those with sophisticated type inference. In that case, "can return null" or not makes a lot of sense for auto-generated documentation or making it an error to compare the return value to null.
However, I agree that
looks a lot less messy than There's just something deeply unsatisfying with a statically typed language that requires so much legalese and boilerplate as Java while still permitting surprising runtime errors in virtually every executable line of the program. Abandoning static types is one approach to the dilemma, trying to plug hole by hole is another.Say you have a function that is basically a loop processing a list of files, it throws some kind of "file not found" error. You could sort the file out and do a resume "try again", select a different file and do resume "diff file (filename)" or do resume "skip".
However, the suggested encoding of an option type in Java blends poorly with subtyping because a JOption<MySubtype> is not a subtype of JOption<MySupertype>. For a quick optional-return scenario, it may work, but as a substitute for nulls in optional parameters, you'd have to remember the precise type of every parameter and create non-empty JOption instances in a rather verbose way.
As an alternative, you could use a tool like Findbugs and its @Nullable annotation.