The worst mistake of computer science

As "The Solution" says, the problem isn't that special-case values exist, it's how they are handled. But perfection doesn't really exist - to some extent, imperfectly-handled special values are impractical to avoid. It's possible, but there's a big price to pay.

A Maybe type with a Nothing case is a big help, but in Haskell (the language where I'm most familiar with this) it's certainly not a perfect solution.

First off, there are "partial" functions - functions which assume that Nothing (or some other value) cannot happen. That's no different to having code that fails to handle NULL. OK, the actual run-time behaviour is different from what you get in C or C++ trying to dereference a NULL, but it's still a run-time error. Actually the behaviour for dereferencing a NULL in C or C++ isn't defined to be a crash - it isn't defined at all. It's perfectly legal for C and C++ compilers to include null checks, with debug-mode compiler options it's sometimes done in practice, and some languages with potentially-null pointers/references require run-time null checks whenever the compiler cannot prove them unnecessary.

One answer to that is that incomplete functions are bad style, which is true to a point, but sometimes the only way to avoid allowing incomplete functions would require defining lots more types. If some value is impossible in some context and some function needs to assume that, a new type is needed which doesn't include that value. Keeping the same type and having a run-time check seems reasonable, but I didn't say "large function" - Haskell is full of fine-grained anonymous functions that are buried in expressions or hidden behind do-notation, list comprehensions etc. ALL those trivial little functions - potentially hundreds of them in what the programmer thinks of as one function - are affected.

Second, in Haskell, there's a value undefined. It's a weird value, recursively defined as...

undefined = undefined

Any language has the possibility of non-termination. In Haskell, due to lazy evaluation, non-termination is basically a value - it's fine to have an undefined value and pass it around as long as you don't actually use it. But if you do try to use it, your code goes into a non-terminating loop.

So it's a slightly odd value in that it doesn't have a representation except in the structure of lazy-evaluation thunks, and even then it doesn't have a unique representation (undefined itself always gives the same form, but infinitely many other nonterminating expression structures exist).

And it's a special value in every type that you cannot test for, because testing for it would mean "forcing" that non-terminating expression, causing that non-terminating loop. The only way (in general) that you can know an expression is non-terminating is by waiting to see if it terminates or not.

Luckily, common cases - such as undefined itself - are detected in practice. Also, in practice it's not a huge problem, though it does cause occasional bugs - which is in essence the average C programmers view of NULL.

Given the halting problem, it's a hard problem for languages to fix - though a few languages do so by requiring the programmer to provide proofs of termination that are be checked at compile time. In theory there are reliably terminating programs that cannot be written in such languages, but it's impossible to give an example.

Final note - non-termination functions are a particular kind of incomplete function, so this non-termination special-case issue isn't limited to languages with lazy evaluation, it's just shows up a bit differently.

/r/programming Thread Link - lucidchart.com