[Haskell-cafe] Doubting Haskell

Alan Carter alangcarter at gmail.com
Sat Feb 16 17:05:47 EST 2008


Greetings Haskellers,

I'm a Haskell newbie, and this post began as a scream for help. Having
slept on it I find myself thinking of Simon Peyton-Jones' recent
request for good use cases. Perhaps a frustrated - and doubting -
newbie can also provide a data point. If my worries are unfounded (and
I hope they are), I think it's significant that to me, today, they
seem real enough. Please understand that I'm not being negative for
the sake of it - rather I'm describing what Haskell looks like from
the outside.

Let me put it this way. Imagine that two weeks ago my forward-thinking
and risk-embracing boss asked me to evaluate Haskell for the upcoming
Project X. Further imagine that she ensured I was able to sit in the
corner emitting curses for the whole two weeks, and on Monday I have
to provide my report.

At this point, two weeks in, I would be forced to say that I have no
reason to believe that Haskell is useful for real world tasks. ghc is
an industrial strength compiler for a toy language. While remarkable
claims are made for it, in practice even the experts are often unable
to implement the most basic behaviours, and where they are able to
implement, they find that their program has become so complex that
they are unable to describe or discuss the result. Likely this is a
deep problem, not a shallow one. The Haskell community is in denial
over this, leading to phenomenal time wasting as one goes round and
round in circles playing word games with documentation. This risks a
return of the chronic embuggerance that we thought we'd escaped when
Vista appeared and the set of people who would have to write Windows
device drivers reduced to Hewlett Packard employees, Joanna Rutkowska
and criminals. When people enthuse about Haskell, we should run a
program called Cat.hs from the haskell.org website, throw fruit at
them and laugh.

Strong words, but in all honesty I *want* to believe, and if I would
make such a report I imagine hundreds if not thousands would say the
same thing. I'm hoping I'm wrong about this, and what's actually
needed is some work on communication (perhaps from a production
programming point of view, which I'd be keen to help with).

What got me started with Haskell was the video of an Intel employee
holding a Teraflops in his hand. I still remember the very silly
September 1991 edition of Scientific American, which asked if a
Teraflops would *ever* be built. What a stupid question! Stack up
enough VIC20s and eventually you'll get a Teraflops. The question
should have been "when". Now it's the size of a CD, and only 80 cores
are needed. Unfortunately keeping 80 cores running is tricky. I know
this from writing some heavy parallel stuff in the mid-90s. It was all
quite clever in it's day. Chuck bloated and unguessable CORBA, do
something light with TCP/IP (Beuwolf took that to extremes). Neat
linkage like rpcgen gave C, so that I could run fast on an SMP Sequent
with 30 cores or on a floorfull of about 70 Sun pizza boxen at night.

Unfortunately despite having a nice framework, tracing rays is still
hard (the rays and medium were... interesting). Making a problem
parallel required a sneaky and dedicated person's sincere skull-sweat.
Worse, the solutions so produced had a horrible structural instability
about them. Just a small change to the requirement could require a
computed value where it wasn't needed before, so that it resulted in
big changes to the implementation. The skull-sweating would be needed
all over again. (Remember that the big point about objects, which e.g.
Booch always emphasized, was that a well chosen set of classes maps
well to the domain and so reduces such structural instability.) Even
then, it was devilish hard to keep 70 "cores" busy.

So watching the Intel guy got my klaxons going. We now need to be able
to do parallel with ease. Functional programming just got really
important. It's years since I last played with Scheme, but I quickly
moved on because I could see the "which Scheme" problem becoming a
millstone round everyone's necks outside of research contexts. Ditto
Lisp. So Haskell. Grown-up compiler, one standard and (apparently) a
decent corpus of example code and tutorials. I might be an imperative
programmer, but I do lapse - for example I find it very easy to
generate swathes of cross referenced documentation using m4. My head
goes kind of weird after a few hours, such that m4 seems sane and it's
the rest of the world that's ungainly, so maybe it should be banned
like DMT, but I like it. I felt able to enter the functional world.

I'll omit the first week of suffering, which I see is a well
documented rite of passage. (Although most evaluators will have left
the building by the end of week one so it's not helping popularity.
Perhaps there could be Squires of the Lambda Calculus who haven't done
the vigil, mortification of flesh and so on?) Eventually a 3 page
introduction on the O'Reilly website together with a good document
called "Haskell for C Programmers" got me to the point where I could
access "Yet Another Haskell Tutorial", and I was away... for a bit.

After a while I'd written some malformed horrors as answers to
exercises, and was telling myself that it's probably just an edge
effect - deep within a real Haskell program the ugliness would be
invisible, tucked away in Ugly.hs. Then I discovered wxHaskell and got
very excited. I really like wxWidgets, and I know it well. If I could
play with some Haskell which manipulates wxWidgets I'd progress very
quickly! I even have a recent C++ wxWidgets  program which was
annoying me as I wrote it because of the boilerplate. Great! I can
play with type inference and mutter "about bloody time" with a smile
on my face. Eventually I got a mix of wxWidgets 2.6.3, wxHaskell from
darcs, ghc 6.6 running on my Mac, almost exactly as that permutation
was described on the website, and I was off.

Within a few hours I had a nice frame, with 10 text controls and
legends populating it. It already took less lines than C++, and then I
discovered how to bundle all the text controls into a tuple to pass
them around, and define some "getters" to access the tuple. My line
count shrunk to what it "should" be - that is, something Kolmogorov
wouldn't laugh at but is unattainable in C++. I had an onOpen which
popped up a dialog and got a filename from the user, with all the
properties filled in right. I proved that I could update the values in
the text controls from the onOpen. Great. Next, translate the bit that
says (pseudocode):

  if(attempt_file_open)
    if(attempt_file_read)
      process

That's it. No fancy, complex error messages. Just check the error
returns and only proceed if I have something to proceed with. Like
grown-ups do. I *will* check my error returns. I have tormented too
many newbies to *ever* consider doing anything else. If I cannot check
my error returns I will not write the program. This is production
programming 101, and I suspect that there's a real issue with
priorities between the traditional Haskell community and production
programmers.

So the time of madness began. I could find examples which did:

  if(attempt_file_open)
    attempt_file_read
    process

Which is useless. Other examples did:

  attempt_file_open
  if(attempt_file_read)
    process

Which is also useless. So I'm looking through the wxHaskell examples.
They're all contrived, using very high level functions to (for
example) read a complete image structure from a named file, which as
one function had one possible source of errors. I go back to scanning
and Googling like crazy. Eventually I notice a bit in "Yet Another
Haskell Tutorial" - page 65 - which introduces "bracket", immediately
explains that it is useless for my (very common) need, and refers the
reader to Section 10 - "Advanced Techniques". The author of this
excellent document hasn't yet written Section 10. I wonder why. I
pause to examine bracket some more. I really can't tell if the
"always" clause gets called if the initialization fails. From the use
with file opening it looks like it isn't (or the handle to be closed
would be invalid) but that wouldn't help if the initializer had two
steps, e.g. called socket(2) and bind(2). This is the kind of thing
good production programmers get really het up about.

I'm still grovelling through reams of stuff, trying to find out how to
open *and* read a file in a grown up way, and I find various programs
on haskell.org. There's an implementation of cat(1)! That's the thing!
A file concatenator must be able to open *and* read it's file. Eagerly
I download it. Curiously there doesn't seem to be any error handling.
I compile it and point it at a non-existent file. The program crashes.
Horribly. So much for Cat.hs. I feel glad I hadn't been able to cope
with the video of Simon Peyton-Jones OSCON talk, because the camera
operator kept filming his face while he's gesturing at the specific
line of code he's talking about with a pointer! After seeing Cat.hs do
essence of FAIL just opening a file, claims that Haskell could serve
as a scripting language suitable for the crew of the Black Pearl in
yonder corner to use would pain me.

Now I'm getting cross and turned off the whole business.  I've been
here before, grovelling through reams of stuff, trying to find
something out while each example is contrived to side-step the
problem, half-baked and useless, evasive or referencing non-existent
documentation. All I need to make the experience complete is a certain
firm's trademarks gratuitously embedded in the text at least once on
every line. Then I'd be nauseous by now too.

Finally I found an uncommented example with no discussion called Hedi.
This seems to be doing exception handling, but what it is doing, how,
why, what can raise them, so on and so forth would presumably be
covered in the "too complex to describe" bit of "Yet Another Haskell
Tutorial". I tried to understand it from first principles, looking at
the Type theology for the exceptions and various calls, but while I
have the cup of tea I lack the necessary Total Perspective Vortex. I
felt no confidence trying to even cut and paste it.

So question number one: Please - how do I do this?

Somehow I doubt that wrapping the complexity of opening a file *and*
reading it in a grown up way and then documenting idioms that use the
wrapper would help. If it would, the wrapper and doco would already
exist. That implies that the complexity doesn't stack. If I get to the
point where I can open a file *and* read it in a grown up way, and
then I need to (say) verify a version number from the file:

  if(attempt_file_open)
    if(attempt_file_read)
      if(version_ok)
        process

Would I need to completely restructure my program? I suspect so. But
it's worth bringing it up. Question number two: Might wrapping the
indescribable complexity of the most basic operations make it possible
to publish and discuss some idioms which accomplish them? I'd be quite
happy with a voodoo idiom for now. I know someone who (20 years on)
still doesn't understand pointers to functions. She still uses
qsort(3) by rote, and it works for her. That will do for beginners.

My biggest worry is that there's a really bad trade-off going on here.
Sure, the structural instability of imperative parallel algorithms can
be reduced, but at the cost of adding structural instability to
everything else! TANSTAAFL and the lump in the carpet always goes
somewhere. I read one chap who complained that in order to add one
single line of debug he had to completely restructure his entire
program, and then the line count was as big as an imperative language
would be. Plus a world more complex I bet. It's this TANSTAAFL that's
making me a non-believer.

So question number three (rhetorical): What would have happened if
Codd and Date had tried to make SQL a general purpose programming
language? Sequential file handling would have been impossible for all
practical purposes, so no-one would have got the benefits of set
theory (oh sorry relational calculus) and our most successful
intentional language would have been stillborn. We'd still be using
CODASYL.

Which leads to question number four: Why not do one job well? Limit
parallelism to a single SMP machine where there are no comms to fail
and failures which do occur will justify the OS chucking the whole
process. Make an interface so that Haskell can be called from C++ or
Java, with a simple IDL that can marshall nested structs, list<>s,
map<>s and vector<>s in and out. Then we can get on with writing
ambitious pure computations with names like sortOutRawCatScanData,
tersely and in easily parallelizable ways, like we embed SQL to get
certain specialist jobs done.

Then when all this was going on, question number five appeared: What
the hell are these "lightweight Haskell threads"? Are they some kind
of green threads running non-preemptively inside a single OS thread?
Are they OS threads that could run concurrently on multi-core
hardware? If they are green threads (and it sounds like they are) then
this is all an academic exercise which has no production application
yet.

I'm still hoping that this is solvable. That the instinctive
priorities of production programmers are just different to those of
researchers, and in fact it *is* possible to open a file *and* read
it, checking *both* error returns, without being driven insane. If so,
I sincerely suggest an example or two, like the small but well formed
programs in K&R, Stroustrup or Gosling saying things like:

  if((fp = fopen(...)) != NULL)
  {
    if(fgets(...) != NULL)
    {
      printf(...);
    }

    fclose(...)
  }

Best wishes - and still hoping I'm wrong after all

Alan Carter


-- 
... the PA system was moaning unctuously, like a lady hippopotamus
reading A. E. Housman ..."
  -- James Blish, "They Shall Have Stars"


More information about the Haskell-Cafe mailing list