47 comments

[ 3.0 ms ] story [ 104 ms ] thread
(comment deleted)
Yes, by all means force people to check for nulls when null is a semantically valid value! But much more importantly, in statically typed languages, allow people to distinguish between nullable and non-nullable types to prevent the most common null safety issue: accidentally passing a null value into a routine whose precondition forbids null, or storing null in a variable whose invariant forbids null.
Essentially this. Some pointers are null, some are not, and the type system should let you express this.
The author noted it, but still, all listed Optional/Maybe “disadvantages” apply to Java specifically and don't really apply to the languages where it was available from the beginning and has some kind of syntax support.
It doesn't even need syntax support, if you are comfortable with higher order functions. Java's approach of forcing the same "check, use" pattern on you makes their Optional only a modest gain. Compare with Haskell's maybe function:

    maybe :: b -> (a -> b) -> Maybe a -> b
You provide a default result, a function from the value (if it's there) to that result type, and the optional value. There is no way to use this wrong - you can't forget to check because there is no check.

Edited to add: Apparently Java does hand you the tools to do it right, if a little ugly, using ifPresent and a purpose-built Consumer. My Java is rusty, but I'll try and put an example together in a bit.

So, you can do the following:

    interface OverOptional<S,T> extends Consumer<T> {
        public S getResult();
    };

Which allows you to create an anonymous class that embodies the first two arguments of maybe:

    Optional<Integer> maybe_int = Optional.of(7);

    OverOptional<Boolean, Integer> is_seven_or_empty
            = new OverOptional<Boolean, Integer>() {
        private Boolean result = true; // default

        public void accept(Integer i) {
            result = i == 7;
        }

        public Boolean getResult() { return result; }
    };

    maybe_int.ifPresent(is_seven_or_empty);

    return is_seven_or_empty.getResult();
Nice article. I thought it was going to be superficial and rehashing common ideas but the author covers a lot of ground and languages in this overview.

Of all the languages I've encountered with null/Option/Optional/Maybe in, the one I think is the most interesting is Ceylon.

Ceylon supports sum types, so you can say that a type is "Account" (non nullable) or "Account|Null" (which can be abbreviated to "Account?").

The compiler will not let you dereference a nullable type, you will have to switch/type cast it first:

    Account? account = ...
    if (exists account) {
      // account has type Account, safe to dereference
    } else {
      // account has type Null, can't dereference
    }
http://ceylon-lang.org/
This is essentially how it is in Haskell, Scala, Swift, etc.
Not really. Haskell and Scala use Maybe/Option, which are monads enabling the composition of operations, even in the absence of values. This leads to a more powerful abstraction (composition) at the expense of heavier syntax and the necessity of libraries that are monad aware.

Ceylon's approach is more of a middle ground with limited composability but straightforward syntax and a much simpler type system.

I'm not sure what this means. In haskell you are free to use monads "the Ceylon way" if that's what you want to do; there is no reason you'd have to leverage the composability of monads if you don't want its added convenience. There is no added monadic overhead if you don't use Maybes in that way.
No, in Haskell and Scala Maybe and Option are sum types. There also happen to be monad operations defined on those types, but you don't have to use them.
Isn't the difference between compiler-enforced null checking and Option/Maybe type (+no null) mostly superficial? Eg compare and contrast the PPL example from the article:

    const nullable string email = system.console?.ask_string ( "Please enter Alice's email address: " )

    if email is not null then
       system.out.write_line ( """Alice's email address is {{email}}""" )
    else
       system.out.write_line ( "Alice doesn't have an email address." )
    end
to hypothetical option type:

    const option<string> email = system.console?.ask_string ( "Please enter Alice's email address: " )

    if email.is_present() then
       system.out.write_line ( """Alice's email address is {{email.get()}}""" )
    else
       system.out.write_line ( "Alice doesn't have an email address." )
    end
Yup. It's mostly a mental hint to the programmer that "HEY this thingie could be null!".
Not with a good type system, i.e. a type system which supports algebraic types (or equivalent) and which explicitly disallows null. With such a type system you wouldn't be able to write the second example as is since string interpolation/concatenation wouldn't be defined for option<string>, only string. You would write something like:

  const option<string> maybeEmail = ....

  case maybeEmail of
      Some email -> system.out.write_line("....{{email}}")
      None -> system.out.write_line("Alice doesn't have an email address.")
The thing to notice here is that we're accessing "email" and not "maybeEmail" in the Some case. This guarantees that we cannot get a null value (since the possibility has been eliminated by the case match).

This simple example doesn't do it justice, however. The real benefit comes when you have functions taking option<X> vs. X arguments and/or returning option<X> vs. X. If you get a plain X (either as a return value or parameter) you know that it cannot be null. No such luck in a language which permits "null" as a value for all (reference) values.

Of course algebraic data types go much further than just this. I'd encourage you to look into them in e.g. Haskell or some variant of ML.

How can they mention Ceylon, Eiffel, Kotlin, and PPL (the latter two I've never heard of, and I've heard of a lot of programming languages) and not mention Haskell, ML, OCaml, etc? It's been mentioned in other comments, but essentially all of the supposed downsides of the Optional solution are implementation weaknesses in either the type system (allowing null anywhere, regardless of type) or runtime (there being overhead of Options) - which seems like a pretty weak argument for loving null.
This post seems somewhat strawman-ish to try and sell the author's language.

No one has ever argued that the reason for nulls being a problem is because the idea of 'nothing' isn't valid; the argument has always been that they are completely invisible to the client function; that is, if you tell me functionA returns a string, I don't know if it -always- returns a string, or if it might also return a null. And you as the writer of functionA might not even know, if you're dependent on still other functions whose contracts are also ambiguous.

To that end, to fix it, a type system to make the implicit null explicit, has always been the necessary answer. It -is- in fact what Option/Maybe/etc are intended to do, and provided they're used in a language that has a sufficiently robust type system, and that the language checks for non-exhaustive patterns in case matches (or similar), are still 100% effective.

All of this author's issues with an Option type are explicitly with Java's implementation of it, a fact he himself notes.

When he's able to show why his language provides better null protection than Haskell or Ocaml or something, I'll be interested. But as it stands, it sounds like he's just making an Option type that is a keyword in the language ("nullable a" rather than simply "a"), rather than a natural extension of the type system (Maybe a = Nothing | Just a).

EDIT: This is especially galling as he's clearly aware of at least Haskell (he mentions that Haskell has the Maybe type), but then dismisses Option/Maybe at large for the reasons he dismisses Java, despite none of the arguments used against Java applying.

Were the author to rewrite this, detailing the basic problem of null being that of no contractual check for it (rather than a strawman of there being no reason to have an 'empty' value), were to rewrite the Option/Maybe bit to show he understands it works fine so long as the language literally does not allow null, except as the Nothing part of the Optional/Maybe type, and instead sell PPL as being an imperative (so no need to drink the functional koolaid), simply typed (no ADTs, no covariant/contravariant OOness) language that has the same no NPE guarantees as Haskell or similar (using effectively the same mechanism; that nulls can only exist when the function contract explicitly declares them as a possibility, and that any consumer of the function must have a code branch to check for it and handle it in some way), it would actually be somewhat interesting; I could see a language like that making headway on the JVM, as it basically would come across "As simple as Java, without the NPEs", which would be a pretty easy sell.

The remaining mystery is why someone's undergraduate-homework-level mistake got voted to the front page.
Indeed. The author does not seem to appreciate (or comprehend) the difference between types 'a' and 'a option'. This is not surprising: I have seen architects that never did anything else than java|python struggle with the basic types in OCaml| Haskell because they could not translate the concept of a value into Java. They always ask "yeah, but what if it's None?" and the answer "It can't be None as that is of a different type" does not seem to compute.
As a Pythoneer with a vague knowledge of Haskell, that's odd.

None is not an int. Or a float. Or a string. It's None. Python is nothing like C, where a string is a pointer that might point to null ... it just can't happen.

Variables are untyped (so you can, in theory, return an open file when the function usually returns a float), but generally that's not how things work.

I'd think Python programmers would get more upset about when stuff gets computed. "So ... it computes that next?". "Um, that's up to the compiler". "The what?"

I think the misunderstanding goes like this -

"Okay...so you return either Maybe (value), or Nothing. But what if they return Maybe (Nothing/null)?" "Can't happen." "What do you mean it can't happen?" "It literally can not happen. The compiler's type checking enforces that when a function returns Maybe A, it really is of type A, which can not be null or nothing.".

And that's where both sides sometimes break down; Pythonistas can, yes, have a hard time understanding that the compiler can enforce that, and Java people can have a hard time getting past the idea of null being a possibility everywhere.

I find it easier to explain coming from the other direction.

"When does a value get assigned null?" "Umm..." "Either you have a variable that has never been given a value, if the language lets you, so is defaulted to null, or you assign the variable to null." "Ok." "Now, you can only assign the variable to null if either you do that explicitly (a = null), or, you are calling out to another function that returns null (a = func()). But then, that function can only return null in the same cases; either the author explicitly assigns null, or he calls another function that returns null." "Right..." "So eventually, to have a null bubble up, someone had to assign or return a null. So your two cases, really, are either you left something uninitialized, or someone assigned/returned a value of null" "Gotcha." "So what if our language prevents you from leaving variables uninitialized, -and- there is no null value?" "You can't have an NPE. But what if there is no valid value? Do you just throw an exception or something?" "You -could-, but that's not ideal. What we do instead, is we change the function signature. That's where the Maybe comes in. Rather than just returning (type), we return Maybe (Type), where the actual values when run are either Just (value), or Nothing. Nothing, in this case, is the equivalent of null, but the caller of the function knows to check for it, and the compiler can be told to MAKE them check for it. And because we have no uninitialized values anywhere, we can't unexpectedly come across a null, and because there are no nulls to assign, anywhere that might be a null is itself a Maybe, and it's handled as such everywhere."

Among the 20 most popular programming languages listed at the Tiobe index (Basic, C, C++, C#, Java, JavaScript, Lisp, Objective-C, Perl, PHP, Python, Ruby and more) none of them has null-safety built-in.

Juste some nuances for Objective-C: in Objective-C, it's perfectly valid to send any message to nil. For instance:

  id someObject = nil;
  [someObject setVisible:YES];
Is a valid code. I really appreciate this language feature, and avoid you a lot of boilerplate test. The problem is that nil as a parameter of a function (selector in Objective-C world) can trigger exception:

  id someObject = nil;
  [myArray addObject:someObject];
will trigger an exception.
My gripe with that is that fail-silent scenarios can lead to some very nasty hard-to-find bugs. I've been bitten by it on several occasions.

Fail fast and with lots of explosions, is the way to go for me.

> and avoid you a lot of boilerplate test.

You call it boilerplate testing, I call it error checking.

Of course code becomes much simpler once you stop checking for error conditions, but it's still a terrible idea to do so (and I think Objective C silently accepting to send messages to nil is a never ending source for hard to find bugs)

I think PPL's nullable semantics are the best solution to the null problems. It makes it clear when you actually need to check for null.
Type systems that incorporate "nullables" allow programmers to have the concept of "nothing was returned" but force them to check it whenever they test for data. Otherwise their programs don't compile (see the Maybe monad in Haskell). In a serious post about null, how could such a serious point be missed?

For example, in Haskell:

  case objectMaybe of
    Just object -> do
      (your code that deals with `object` here)
    Nothing -> do
      (your code that deals with no object)
Without the Nothing clause in the matching statement, this code will not compile.
Not quite.

    sh-4.3$ cat test.hs
    main = let x = Nothing
           in case x of
              Just x -> putStrLn "something"
    sh-4.3$ ghc test.hs
    sh-4.3$ ./test 
    test: test.hs:(2,11)-(3,40): Non-exhaustive patterns in case
Ah, so it will compile but break, thank you for mentioning that.
Not if you're building with -Wall -Werror.
...which is not a real improvement compared to null-values
Alternatively,

    sh-4.3~$ ghc -Wall -Werror test.hs
    [1 of 1] Compiling Main             ( test.hs, test.o )

    test.hs:1:1: Warning:
        Top-level binding with no type signature: main :: IO ()

    test.hs:3:16: Warning:
        This binding for `x' shadows the existing binding
          bound at test.hs:1:12

    test.hs:3:16: Warning: Defined but not used: `x'

    <no location info>: 
    Failing due to -Werror.
This is where it gets weird on my system

    sh-4.3$ ghc --version
    The Glorious Glasgow Haskell Compilation System, version 7.8.3
    sh-4.3$ ghc -Wall -Werror test.hs
    sh-4.3$ 
Arch Linux 64bit. It works as expected with runhaskell though

Edit: see answer below, thanks dilthomas!

Try touching test.hs (or add -fforce-recomp). By default, ghc won't rebuild if the object is newer than the source.
But it doesn't break because of the "Non-exhaustive patterns in case" that causes the runtime failure? These things look like they can be fixed without touching the case statement.
Amazing how close that is to valid Erlang
"There are more methods in Java's Optional class, but they are irrelevant for the discussion in this article."

This statement represents a bad misunderstanding of the point of Optional. The fact that is has map, flatMap, and ifPresent methods is _very_ relevant to the discussion in this article.

> This is a trivial example of a null handling situation. Nevertheless, the only way to know what will happen is to try it out because it really depends on the programming language and the compiler we use. Ask any experienced Java programmer and you will see that most of them (me included) will struggle to predict the actual behaviour without hesitation.

Yes, because what I want to do is waste my time figuring out what happens when I apply functions to null values.

> This is a trivial example of a null handling situation. Nevertheless, the only way to know what will happen is to try it out because it really depends on the programming language and the compiler we use. Ask any experienced Java programmer and you will see that most of them (me included) will struggle to predict the actual behaviour without hesitation.

Yes, because what I want to do is waste my time figuring out what happens when I apply functions to null values.

These bugs are notbcauswd by null they are caused by M is understanding type systems and null itself.
The problem with NullPointerException is that it usually comes with a high error-failure distance (that is, the failure happens later than the logical error that caused it). It can be extremely hard to debug, because null is actually an overloaded concept. It ends up meaning:

    1. Like NaN, the result of an error (in some languages). 
    2. A pre-initialization value that "should never appear" by informal contract. 
    3. Missing data (especially in databases).
    4. Not-applicable. 
    5. "There is none" (not quite the same as #3 or #4).
    6. A return on an unusual (and bad) condition, like a malloc that can't. 
    7. (Infuriatingly common.) Errors in programming.
    8. Empty collections (from Lisp, although that's probably a design error 
       and Clojure's moving away from it.)
I consider a NullPointerException for any reason to be a bug in a Java program. A more useful error message should be given. I also consider null in Scala to be something that ought to be disallowed. (In Clojure, you can't be as hard-assed about it, because nil is actually useful. But I'd still argue that NPE is always an error.)

Zero-as-null is pretty awful, I agree. When you see that, it's usually as a performance optimization. For example, prices on exchanges are usually represented as integer numbers of pennies (or some smaller "microcurrency" unit) for regulatory reasons (accuracy standards set in decimal terms, disallowing floating point) and the data is unboxed for performance reasons, requiring some "illogical" sentinel value when the number isn't applicable.

The effect of that is to externalize complexity to the client. For example, if you use "0" for "no bid" that's usually harmless because "no buyer" and "highest buyer at $0" are effectively equivalent. But 0 is also used for "no offer" and (if the client isn't aware of the 0's meaning) might make incorrect conclusions, since an ask of 0 (commodity given away for free) means the opposite of "no ask". The client is forced to keep track of this extra complexity-- that 0.0 doesn't mean a numerical value but means there is none.

Null policy (over all the different causes of "null") is really about how the complexity is externalized to the client. Zero-as-null forces the client to deal with "zero is not zero" possibilities, which can be deadly. The best, if the API can support it, is to use Haskell-style optional types (Maybe). You can't get away from the intrinsic complexity, but you can at least require in the type system that the programmer deal with it. (Non-exhaustive pattern-match warnings should generally be treated as errors.)

The problem with, say, Java's references, is that every object type has null as an additional value. Instead of a Spoon, what you actually have is an Maybe (IORef Spoon). All your (non-primitive) types end up being optional, but in a way that is usually sparsely documented.

"I consider a NullPointerException for any reason to be a bug in a Java program."

Is there actually anyone who disagrees on that point?

Surprisingly, you see that sort of issue (perhaps in a more subtle way) a lot, even in evolved libraries and languages.

By "I consider NullPointerException for any reason to be a bug" I mean that, even if the program is going to crash hard, it should give a better error message. Crashing hard is sometimes the right thing, but high error-failure distance is never good.

This may have changed, but for a while, OCaml raised the dreaded (and useless) Not_found error on List.head [], and it wasn't until 3.10 that you got a stacktrace on a runtime error. (Of course, most people who use OCaml or Haskell in production rename those functions to, e.g., unsafeHead and make the optionized versions the default.)

Debugging and error-handling culture has come a long way, even in the past 10 years. Clojure, for example, is a great language and I am overall a huge fan, but it was painful to debug when it first came out, and it used to be fairly common for runtime errors to manifest as NullPointerException.

(comment deleted)
> null is what allows us to state things like:

> Alice's birth date has not yet been entered in the database (alice.birth_date = null)

Better solved with Maybe (see other references in threads)

> No orders have yet been placed by this customer (customer.order_list = null)

Should be an empty list

> The operation succeeded without any errors (error = null)

The Either data type is a good solution here, because you can prevent accidentally operating on an invalid result if there was an error

> There is no more beer in the fridge (fridge.beer = null)

Should be 0 or an empty list

As I understand it, this article misrepresents the purpose and nature of the Null Object Pattern ("NOP").

The author cites a comment that describes (very) vaguely a "hotel booking" example, expressing the idea that "None" is the null object. But "None" is not an instance of the NOP. None has no interface. You can't tell None to do something.

In contrast, an acutal implementation of the NOP might be in a travel reservation service which allows you to reserve both a hotel and a rental car at the same time. Somewhere in the guts of that service there might be a Trip object.

  class Trip
  {
    Car car;
    Hotel hotel;
  
    public void Reserve()
    {
      this.car.Reserve();
      this.hotel.Reserve();
    }
  . . .
  }
A Trip can be constructed with a NullCar when the user doesn't want to reserve a car on this trip. When NullCar::Reserve is invoked, it doesn't do anything, but Trip doesn't care, and the implementor of Trip doesn't need to remember to check its car to see if it's null before invoking it.

I've encountered this misconception frequently, and it typically stems from the common understanding of object oriented programming as programming in terms of entities which represent physical objects in the real world. I deduce that the author uses this definition from the sentence "It is also interesting to note that appearances of null objects are virtually non-existent in real life."

Objects aren't necessarily representations of physical entities. Objects are simply things other objects (or procedural code) can use to accomplish something without having to know how to accomplish that thing.

I've found the distinction that Alan Kay has (repeatedly) made useful in helping people understand this idea: Property accessors aren't for objects, they're for Data Structures.

So, when the author says that, "the basic idea of the null object pattern is exactly the same as for practices like 'use zero instead of null'", my reply is, "No, it's not called the 'Null Data Structure Pattern'." Use zero instead of null is (flawed) advice about how to write data structures, not how objects should be constructed. The NOP is not used to build objects that represent data, it is used to build objects that represent action. The author's repeated use of the GetCustomer example is inappropriate for the NOP. It's much more likely that a database record is represented using a data structure than an object.

Pragmatically, if you feel the desire to ask an object if it is the NullObject, you are not writing object-oriented code, you are writing procedural code. Instead of asking if the object is the NullObject, move the branch that would be invoked if the object were the null object into a method present in the interface that the NullObject implements. Perhaps you find that you don't actually have a NullObject, after all.