[Haskell-cafe] head &c Re: how do you debug programs?

Jón Fairbairn jon.fairbairn at cl.cam.ac.uk
Thu Sep 7 01:11:47 EDT 2006


"Neil Mitchell" <ndmitchell at gmail.com> writes:
> I wrote:
> > H'm.  I've never been completely convinced that head should
> > be in the language at all.  If you use it, you really have
> > to think, every time you type in the letters h-e-a-d, "Am I
> > /really/ /really/ sure the argument will never be []?"
> > Otherwise you should be using a case expression or (more
> > often, I think) a subsidiary function with a pattern match.
> 
> I disagree (again...) - head is useful.

Given that a programming language remain computationally
complete, utility is never a sufficient reason for including
something in it.  Guns are occasionally useful, but if you
walk around with a loaded automatic down the front of your
pants, you are *far* more likely to blow your own balls off
than encounter circumstances where the gun would be useful.

A large part of good programming language design is about
preventing you from blowing your balls off (or anyone
else's).

> And as you go on to say, if you apply it to the infinite
> list, who cares if you used head. Head is only unsafe on
> an empty list. So the problem then becomes can you detect
> unsafe calls to head and let the programmer know.

No, that's the wrong approach because it relies on detecting
something that (a) the programmer probably knows already and
(b) is undecidable in general. It would be far better for
the programmer to express this knowledge in the
programme. In Haskell it's not possible for the two
datatypes

   data NonFiniteList t = (:) {head::t, tail::  NonFiniteList t}

and 

   data List t = (:) {head::t, tail::  List t}
               | []

to coexist, and though it would even then have been possible
to use a type system where all the operations on List were
also applicable to NonFiniteList, the means available then
would have lost type inference. So the consensus at the time
was that doing clever stuff with types (making one a
transparent supertype of the other) was too problematic, and
keeping them separate would have meant providing all the
list operations twice. Once typeclasses came along, this
argument no longer held, but redoing lists and nonfinite
lists as special cases of a sequence class would have been
hard work.

I think it's hard work that should have been done, though.

As I said in the post to which you replied, if you use head,
you really have to have a convincing argument that it isn't
going to be applied to []. If you don't have one, use
something other than head (it's always possible). If your
programme crashes with a "head []", the only reasonable
thing to do is to go through all 60 uses and check them. If
you don't have time for that right now, doing an automatic
replace is a reasonable interim measure, so long as you
don't replace them back once you've found the particular
bug.

1
i
import Control.Exception
.
s/head/(\l -> assert (not $ null l) (head l))/g

...

Ugly, and 
*** Exception: /tmp/foo.hs:4:7-12: Assertion failed
is really ugly, but not as ugly as 
*** Exception: Prelude.head: empty list
:-)

And the ugliness might encourage a rethink of particular
uses of head next time you have to look at that bit of code,
and that's a good thing even if all you end up doing is
manually substituting a let for the lambda and adding a
comment on the lines "I really can't see how this list could
ever be empty, but I can't prove it".


-- 
Jón Fairbairn                                 Jon.Fairbairn at cl.cam.ac.uk




More information about the Haskell-Cafe mailing list