Yeah. A major reason null is called the "billion dollar mistake" is that once you finally do get a NullPointerException (or the like), it can take a huge amount of time to track down where the null value originated.
If you take the head of an empty list in Haskell, you get an exception right away. Not a poisoning of the well, like you do in so many other languages.
That's absolutely a benefit of Haskell worth touting.
What makes it easier is immutability - if given an empty list, the only place that could've made this list empty is the place it was constructed - because there's no way some other function can come and delete items from it. A function which "removes" items from a list doesn't actually do such thing - it creates a brand new one and adds all the same elements except the items you requested being removed.
In this way, there's only one possible path that the list could've come from - through the pure functions which use it - until head is reached. Given that each function is referentially transparent, applying the same input list to a function in the debugger will always produce the same result, so it's simple to call a function with some sample data in ghci, and the result you get will be the same result you get in the compiled program.
Debugging/tracing is perhaps more difficult than with tooling you might already be familiar with - but it's much more rare that you need to even use them, because it's obvious what values a function should return - they don't have any state which could influence otherwise.
And there is a billion ways how to construct an empty list. You can't just grep for '[]'. It can be hidden inside of a 'catMaybes', 'tail', or any other function which returns a list and makes no guarantees about it's size, of which there are plenty.
So first of all, head as it is in Prelude is a partial function. The error isn't a null pointer exception, it is an issue with head not being defined on empty lists.
I personally would never use head in code I write. Instead I'd use a version which looks like [a] -> Maybe a which does properly solve the null problem.
On top of that, the compiler can warn/error if partial functions are used. I do this for all of my projects.
There has been a lot of debate over these functions should even existing in Prelude. I think they are a blight on the language.
For example, in the `head []` case, ghc has an option to locate the exact source of the error:
-xc
(Only available when the program is compiled for profiling.) When an exception is raised in the program, this option causes a stack trace to be dumped to stderr.
This can be particularly useful for debugging: if your program is complaining about a head [] error and you haven't got a clue which bit of code is causing it, compiling with -prof -fprof-auto and running with +RTS -xc -RTS will tell you exactly the call stack at the point the error was raised.
The output contains one report for each exception raised in the program (the program might raise and catch several exceptions during its execution), where each report looks something like this:
*** Exception raised (reporting due to +RTS -xc), stack
trace:
GHC.List.CAF
--> evaluated by: Main.polynomial.table_search,
called from Main.polynomial.theta_index,
called from Main.polynomial,
called from Main.zonal_pressure,
called from Main.make_pressure.p,
called from Main.make_pressure,
called from Main.compute_initial_state.p,
called from Main.compute_initial_state,
called from Main.CAF
...
If you take the head of an empty list in Haskell, you get an exception right away. Not a poisoning of the well, like you do in so many other languages.
That's absolutely a benefit of Haskell worth touting.