Revision as of 16:08, 7 September 2012

Errors such as taking

head

or

tail

of the

empty list in Haskell are equivalent to the dereferencing of the zero
pointer in C/C++ or NullPointerException in Java. These
errors occur because the true domain of the function is smaller than
the function's type suggests. For example, the type of

head

says that the function applies to any list. In

reality, it can be meaningfully applied only to non-empty
lists. One can eliminate such errors by giving functions

It must be emphasized that we can eliminate head-of-empty-list errors
now, without any modification to the Haskell type system, without
developing any new tool. In fact, it is possible in Haskell98! The
same technique applies to OCaml and even Java and C++. The only
required advancement is in our thinking and programming style.

As we can see, the null test is algorithmic. After we've done it, head
and tail no longer need to check for null list. Those head and tail
functions are total. Thus we achieve both safety and performance.

We can also write

-- Again, we are statically assured of no head [] error!
test2 =head$1!: 2!: 3!: []

I should point to
Lightweight dependent typing for justification and formalization, as
well as for for further, more complex examples.
We can also use the approach to
ensure various control properties, e.g., the yield property: a thread may
not invoke `yield' while holding a lock. We can assure this property
both for recursive and non-recursive locks.

If there is a surprise in this, it is in the triviality of
approach. One can't help but wonder why don't we program in this
style.

2 Integrating with the existing list-processing functions

Jan-Willem Maessen wrote:

In addition, we have this rather nice assembly of functions which
work on ordinary lists. Sadly, rewriting them all to also work on
NonEmptyList or MySpecialInvariantList is a nontrivial task.

That's an excellent question. Indeed, let us assume we have a function

foo::[a]->[a]

(whose code, if available, we'd rather not change) and we want to
write something like

\l ->[head l,head(foo l)]

To use the safe

head

from NList.hs , we should write

\l -> indeedFL l onempty (\l ->[head l,head(foo l)])

But that doesn't type: first of all,

foo

applies to

[a]

rather than

FullList a

, and second, the result of

foo

is not

FullList a

, required
by our

head

. The first problem is easy to solve: we can always
inject

FullList a

into the general list:

fromFL

. We insist on writing

the latter function explicitly, which keeps the typesystem simple,
free of subtyping and implicit coercions. One may regard

fromFL

as an
analogue of

fromIntegral

-- which, too, we have to

write explicitly, in any code with more than one sort of integral
numbers (e.g., Int and Integer, or Int and CInt).

If we are not sure if our function foo maps non-empty lists
to non-empty lists, we really should handle the empty list case:

That would get the code running. Possibly at some future date (during
the code review?) I'll be called to justify my hunch, to whatever
degree of formality (informal argument, formal proof) required by the
policies in effect. If I fail at this justification, I'd better think
what to do if the result of foo is really the empty list. If I
succeed, I'd be given permission to update the module NList with the
following definition