Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

[Haskell-cafe] Doubting Haskell

239 views
Skip to first unread message

Alan Carter

unread,
Feb 16, 2008, 5:06:12 PM2/16/08
to haskel...@haskell.org
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"
_______________________________________________
Haskell-Cafe mailing list
Haskel...@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe

Albert Y. C. Lai

unread,
Feb 16, 2008, 6:02:09 PM2/16/08
to haskel...@haskell.org
Alan Carter wrote:
> if((fp = fopen(...)) != NULL)
> {
> if(fgets(...) != NULL)
> {
> printf(...);
> }
>
> fclose(...)
> }

This reminds me of a 1976 article written by David Parnas and Harald
Würges: Response to undesired events in software systems. Since it's
old, it is harder to find, but here are a few things to try:

If you have download privilege on ACM Digital Library,
http://portal.acm.org/citation.cfm?id=800253.807717

(If not, you can still see the full citation, the abstract, etc.)

The paper is also collected in this book full of Parnas's papers:
Software Fundamentals: collected papers by David L. Parnas. Edited by
Daniel M. Hoffman and David M. Weiss.

Someone else made slides to present this paper:
http://www.cs.virginia.edu/~wh5a/personal/Quals/misc/ParnasPaper%20on%20stanley/ResponseToUndesiredEvents.ppt

Cale Gibbard

unread,
Feb 16, 2008, 6:50:26 PM2/16/08
to Alan Carter, haskel...@haskell.org
On 16/02/2008, Alan Carter <alang...@gmail.com> wrote:
> 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.
>
> Best wishes - and still hoping I'm wrong after all
>
> Alan Carter

Sorry for missing this question in my first response. The answer of
course depends on the Haskell implementation in question, but of
course, we're talking about GHC here.

Haskell threads, in the sense of Control.Concurrent.forkIO, are
essentially a sort of green thread which is scheduled by the Haskell
runtime system. Threads can either be bound to a particular OS thread,
or (as is default), not be bound to a particular OS thread, allowing
the scheduler to manage n Haskell threads with m OS threads, where
usually you want to set m to something like the number of processors
in your machine.

I'm a little hazy on the details, and perhaps someone more familiar
with the GHC runtime can fill in some more details for you if you'd
like.

Aside from Concurrent Haskell (which was originally designed for
single-processor concurrency and later extended to allow for
scheduling threads to execute in multiple OS threads), there is
Parallel Haskell, which is used to annotate pure computations for
parallelism (but since they're pure, there is no concurrency). At its
core, Parallel Haskell has an extremely simple programmer interface:

par :: a -> b -> b

Evaluation of an expression of the form (par x y) will cause x to be
put in a queue of expressions to be evaluated by a worker on some OS
thread, if there is free time, before resulting in y. If there is no
time to evaluate x on some processor before it is eventually needed,
then evaluation just proceeds normally, but if there is, then it won't
need evaluation later, due to the usual sharing from lazy evaluation.
>From this extremely simple form of parallel annotation, it's possible
to build lots of interesting mechanisms for carrying out evaluation in
parallel. You can read more about that in a paper titled "Algorithm +
Strategy = Parallelism" by PW Trinder, K Hammond, H-W Loidl and Simon
Peyton Jones, or check out the documentation for
Control.Parallel.Strategies.

- Cale

Stefan O'Rear

unread,
Feb 16, 2008, 7:26:25 PM2/16/08
to Cale Gibbard, haskel...@haskell.org
On Sat, Feb 16, 2008 at 06:50:03PM -0500, Cale Gibbard wrote:
> On 16/02/2008, Alan Carter <alang...@gmail.com> wrote:
> > 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.
> >
> > Best wishes - and still hoping I'm wrong after all
> >
> > Alan Carter

Yes, they are green threads. But not the stupid kind you are used to.

Consider an operating system. You are running hundreds of threads in a
typical system. You don't have hundreds of processors - let's be
generous and say you have 8. Obviously these threads are in some sense
'green'. But they are still being run with (limited) parallelism!
There is no reason to expect anything less of user-level 'green
threads', and if all the systems you have been using are incapable of
running threads in paralell, then all the systems you have been using
are toys or broken. GHC is not a toy (in this regard), and contains a
mini-operating system that schedules how ever many millions of threads
you have onto a number of OS threads specified with the +RTS -N<x>
option.

Stefan

signature.asc

Jefferson Heard

unread,
Feb 16, 2008, 7:28:09 PM2/16/08
to Alan Carter, haskel...@haskell.org
Since everyone's been focusing on the IO so far, I wanted to take a
quick stab at his mention of "green" vs. OS threads... I like the
term "green", actually, as it's what my grandmother calls
decaffeinated coffee, owing to the fact that decaf taster's choice has
a big green plastic lid. Distrust all coffee that comes in a plastic
lid, folks. Life is better that way...

However, Haskell very much has real, caffeinated parallelism
mechanisms. There is explicit concurrency, which says that things can
happen at the same time (see Control.Concurrent) and there is the
whole question of Glasgow Parallel Haskell and Data Parallel Haskell,
which I won't really begin to cover, as Manuel Chakravarty and Simon
Peyton Jones will do TONS better at explaining these than I can. I
will however mention Control.Parallel and Control.Parallel.Strategies,
because they're my personal favorite way of being parallel.

The Haskell thread is semantically much like the Java thread, it's
green, in other words, but you can control the number of real OS
threads that Haskell threads are executed on at the command line.
Thus you might call them "half caffeinated" But, at least with
Control.Parallel.Strategies, they're SO much easier to use. There are
a couple of caveats, but I'll give an example first. Let's say you're
doing some heavy computer graphics, but you're doing it all in
spherical coordinates (I do this all the time, which is why I'm using
it as an example) and before you go to OpenGL, you need to transform
everything into Carteisan coordinates.

vertices :: [GL.Vertex3] -- a list of oh, say, 150,000 vertices or so
in spherical coordinates

sphericalToCart :: GL.Vertex3 -> GL.Vertex3
sphericalToCart (GL.Vertex3 r a z) = (GL.Vertex3 (r * cos a * sin z)
(r * sin a * sin z) (r * cos a))

Now to convert them all, you'd just do a

map sphericalToCart vertices

and that would do a lazy conversion of everything, but since you know
you're going to use all the vertices, strictness is just as well, and
you can do strict things in parallel this way:

parMap rwhnf sphericalToCart vertices

or even more efficiently,

map rwhnf sphericalToCart vertices `using` parListChunk 1024

That'll execute on all cores of your processor and do the same
operation much faster, if you were going to have to do the entire
operation anyway.

-- Jeff

> ... the PA system was moaning unctuously, like a lady hippopotamus


> reading A. E. Housman ..."
> -- James Blish, "They Shall Have Stars"
> _______________________________________________
> Haskell-Cafe mailing list
> Haskel...@haskell.org
> http://www.haskell.org/mailman/listinfo/haskell-cafe
>

--
I try to take things like a crow; war and chaos don't always ruin a
picnic, they just mean you have to be careful what you swallow.

-- Jessica Edwards

Donn Cave

unread,
Feb 16, 2008, 8:05:17 PM2/16/08
to Haskell Cafe

On Feb 16, 2008, at 3:46 PM, Philippa Cowderoy wrote:

> On Sat, 16 Feb 2008, Alan Carter wrote:
>
>> I'm a Haskell newbie, and this post began as a scream for help.
>

> Extremely understandable - to be blunt, I don't really feel that
> Haskell
> is ready as a general-purpose production environment unless users are
> willing to invest considerably more than usual. Not only is it not as
> "batteries included" as one might like, sometimes it's necessary to
> build
> your own batteries!

Ironically, the simple task of reading a file is more work than I expect
precisely because I don't want to bother to handle exceptions. I mean,
in some applications it's perfectly OK to let an exception go to the
top.

But in Haskell, you cannot read a file line by line without writing an
exception handler, because end of file is an exception! as if a file
does
not normally have an end where the authors of these library functions
came from?

For the author of the original post ... can't make out what you actually
found and tried, so you should know about "catch" in the Prelude, the
basic exception handler.

Donn Cave, do...@avvanta.com

Bryan O'Sullivan

unread,
Feb 16, 2008, 8:12:22 PM2/16/08
to Donn Cave, Haskell Cafe
Donn Cave wrote:

> But in Haskell, you cannot read a file line by line without writing an
> exception handler, because end of file is an exception!

Ah, yet another person who has never found System.IO.hIsEOF :-)

Whereas in C or Python you would check the return value of read against
zero or an empty string, in Haskell you call hIsEOF *before* a read.

<b

Stefan O'Rear

unread,
Feb 16, 2008, 8:36:28 PM2/16/08
to Bryan O'Sullivan, Haskell Cafe
On Sat, Feb 16, 2008 at 05:11:59PM -0800, Bryan O'Sullivan wrote:
> Donn Cave wrote:
>
> > But in Haskell, you cannot read a file line by line without writing an
> > exception handler, because end of file is an exception!
>
> Ah, yet another person who has never found System.IO.hIsEOF :-)
>
> Whereas in C or Python you would check the return value of read against
> zero or an empty string, in Haskell you call hIsEOF *before* a read.

I'll bet that breaks horribly in the not-so-corner case of /dev/tty.

Stefan

signature.asc

Bryan O'Sullivan

unread,
Feb 16, 2008, 9:24:15 PM2/16/08
to Stefan O'Rear, Haskell Cafe
Stefan O'Rear wrote:

> I'll bet that breaks horribly in the not-so-corner case of /dev/tty.

Actually, it doesn't. It seems to do a read behind the scenes if the
buffer is empty, so it blocks until you type something.

Stefan O'Rear

unread,
Feb 16, 2008, 9:35:07 PM2/16/08
to Bryan O'Sullivan, Haskell Cafe
On Sat, Feb 16, 2008 at 06:23:54PM -0800, Bryan O'Sullivan wrote:
> Stefan O'Rear wrote:
>
> > I'll bet that breaks horribly in the not-so-corner case of /dev/tty.
>
> Actually, it doesn't. It seems to do a read behind the scenes if the
> buffer is empty, so it blocks until you type something.

Well... that's what I meant by break horribly.

Stefan

signature.asc

Bryan O'Sullivan

unread,
Feb 16, 2008, 9:36:36 PM2/16/08
to Stefan O'Rear, Haskell Cafe
Stefan O'Rear wrote:

> Well... that's what I meant by break horribly.

Buh? That behaviour makes perfect sense to me.

Jonathan Cast

unread,
Feb 16, 2008, 9:48:56 PM2/16/08
to Donn Cave, Haskell Cafe
On 16 Feb 2008, at 5:04 PM, Donn Cave wrote:

>
> On Feb 16, 2008, at 3:46 PM, Philippa Cowderoy wrote:
>
>> On Sat, 16 Feb 2008, Alan Carter wrote:
>>
>>> I'm a Haskell newbie, and this post began as a scream for help.
>>
>> Extremely understandable - to be blunt, I don't really feel that
>> Haskell
>> is ready as a general-purpose production environment unless users are
>> willing to invest considerably more than usual. Not only is it not as
>> "batteries included" as one might like, sometimes it's necessary
>> to build
>> your own batteries!
>
> Ironically, the simple task of reading a file is more work than I
> expect
> precisely because I don't want to bother to handle exceptions. I
> mean,
> in some applications it's perfectly OK to let an exception go to
> the top.
>
> But in Haskell, you cannot read a file line by line without writing an
> exception handler, because end of file is an exception! as if a
> file does
> not normally have an end where the authors of these library functions
> came from?

I agree 100%; to make life tolerable around Haskell I/O, I usually
end up binding the moral equivalent of

tryJust (\ exc -> case exc of
IOException e | isEOFError e -> return ()
_ -> Nothing) $
getLine

somewhere at top level and then calling that where it's needed.

> For the author of the original post ... can't make out what you
> actually
> found and tried, so you should know about "catch" in the Prelude, the
> basic exception handler.

Also, you might need to know that bracket nests in various ways:

bracket openFile hClose $ bracket readLine cleanUpLine $ proceed

There's also finally, for when the first argument to bracket is
ommitted, and (>>) for when the second argument is :)

jcc

Colin Paul Adams

unread,
Feb 17, 2008, 4:13:22 AM2/17/08
to apfelmus, haskel...@haskell.org
>>>>> "apfelmus" == apfelmus <apfe...@quantentunnel.de> writes:

apfelmus> Colin Paul Adams wrote:
>> Left? Right?
>>
>> Hardly descriptive terms. Sounds like a sinister language to
>> me.

apfelmus> The mnemonics is that Right x is "right" in the sense of
apfelmus> correct. So, the error case has to be Left err .

As I said, this is sinister (i.e. regarding left-handed people as
evil).

And left is not the opposite of correct. That would be incorrect.

Also, it is not clear to me that a failure to read a file (for
instance) is incorrect behaviour. If the file doesn't exist, then I
think it ought to be considered correct behaviour to fail to read the
file.

So Success and Failure seem to be much better. Certainly they make the
program far more readable to my eyes.
--
Colin Adams
Preston Lancashire

Jonathan Cast

unread,
Feb 17, 2008, 4:21:06 AM2/17/08
to Colin Paul Adams, apfelmus, haskel...@haskell.org

On 17 Feb 2008, at 1:12 AM, Colin Paul Adams wrote:

>>>>>> "apfelmus" == apfelmus <apfe...@quantentunnel.de> writes:
>
> apfelmus> Colin Paul Adams wrote:
>>> Left? Right?
>>>
>>> Hardly descriptive terms. Sounds like a sinister language to
>>> me.
>
> apfelmus> The mnemonics is that Right x is "right" in the sense of
> apfelmus> correct. So, the error case has to be Left err .
>
> As I said, this is sinister

You do know what `sinister' means, no?

> (i.e. regarding left-handed people as
> evil).

Sheesh, it's just a mnemonic...

>
> And left is not the opposite of correct. That would be incorrect.

No, Left is the opposite of Right. Right is the constructor modified
by fmap (due to the design of Haskell type classes); therefore return
= Right. Therefore any computation in Either that is not the result
of a return is an application of Left.

> Also, it is not clear to me that a failure to read a file (for
> instance) is incorrect behaviour.

Then don't think of Left as `incorrect behavior'. Left isn't
incorrect, or Parsec's parse function wouldn't return it on parse
errors.

> If the file doesn't exist, then I
> think it ought to be considered correct behaviour to fail to read the
> file.
>
> So Success and Failure seem to be much better. Certainly they make the
> program far more readable to my eyes.

But the program succeeded in doing what I expected it to do when if
failed...

jcc

Besides, these decisions were made 15 years ago, they're not going to
change now...

Philippa Cowderoy

unread,
Feb 17, 2008, 8:32:53 AM2/17/08
to Anton van Straaten, haskel...@haskell.org
On Sun, 17 Feb 2008, Anton van Straaten wrote:

> Is there a benefit to reusing a generic Either type for this sort of thing?
> For code comprehensibility, wouldn't it be better to use more specific
> names? If I want car and cdr, I know where to find it.
>

It's Haskell's standard sum type, with a pile of instances already
written. There's an instance of MonadError such that you only need to see
an Either when you run the computation for example (and then you get an
Either whatever the actual error monad was!). If we had appropriate
language extensions to map an isomorphic Success/Failure type onto it then
I'd probably use them - as it is, the level of inertia around Either is
great enough to mean that's only worth doing if I'm expecting to roll a
third constructor in at some point.

That said, generally I'll wrap it up pretty fast if I have to handle
Either directly. Not that that's necessarily any different to cons, car
and cdr of course, but there's plenty of library support for doing so.

--
fli...@flippac.org

"I think you mean Philippa. I believe Phillipa is the one from an
alternate universe, who has a beard and programs in BASIC, using only
gotos for control flow." -- Anton van Straaten on Lambda the Ultimate

Donn Cave

unread,
Feb 17, 2008, 12:56:49 PM2/17/08
to Haskell Cafe
On Feb 17, 2008, at 1:12 AM, Colin Paul Adams wrote:

>
> And left is not the opposite of correct. That would be incorrect.
>
> Also, it is not clear to me that a failure to read a file (for
> instance) is incorrect behaviour. If the file doesn't exist, then I
> think it ought to be considered correct behaviour to fail to read the
> file.

Well, of course correct behavior is to cope with both cases in
the most appropriate way.

If it's any consolation to those of the left handed persuasion, I
guessed
it wrong - I have used Either in this way, but Left was Success and
Right was Failure. I don't enjoy puns, and mapped to an A/B form
it seemed obvious that Success is A.

Donn Cave, do...@avvanta.com

jerzy.kar...@info.unicaen.fr

unread,
Feb 17, 2008, 1:28:32 PM2/17/08
to Haskell Cafe
Donn Cave writes:

> On Feb 17, 2008, at 1:12 AM, Colin Paul Adams wrote:

>> And left is not the opposite of correct. That would be incorrect.

..


> If it's any consolation to those of the left handed persuasion, I guessed
> it wrong - I have used Either in this way, but Left was Success and
> Right was Failure. I don't enjoy puns, and mapped to an A/B form
> it seemed obvious that Success is A.

Weellll, for those who don't enjoy puns, but feel that the life and
everything is one enormous pun, a political reminder.

For many years, the world is composed of Leftists and Rightists (let's
for the moment forget the normal people). Those from the Left always felt
that they were right, and that those from the Right should not be left
unpunished, while those from the Right thought that those from the Left
should be left to die. Even if it seems right to consider that these
deviations should be left to historians, we should not forget that at
the beginning of the glorious Soviet country there was a proposal to
change the meaning of traffic lights. Red would mean "Forward!!".

On the other hand, in France nowadays the difference between Right and
Left is more or less the same as between "Immediate failure" and "Delayed
failure". Choose yourselves which is which.

Jerzy Karczmarczuk

John Meacham

unread,
Feb 17, 2008, 8:09:42 PM2/17/08
to haskel...@haskell.org
On Sat, Feb 16, 2008 at 05:04:53PM -0800, Donn Cave wrote:
> But in Haskell, you cannot read a file line by line without writing an
> exception handler, because end of file is an exception! as if a file does
> not normally have an end where the authors of these library functions
> came from?

Part of it is that using 'getLine' is not idiomatic haskell when you
don't want to worry about exceptions. Generally you do something like

doMyThing xs = print (length xs)

main = do
contents <- readFile "my.file"
mapM_ doMyThing (lines contents)


which will call 'doMyThing' on each line of the file, in this case
printing the length of each line.

or more succinctly:

main = readFile "my.file" >>= mapM_ doMyThing . lines


John

--
John Meacham - ⑆repetae.net⑆john⑈

Wolfgang Jeltsch

unread,
Feb 19, 2008, 5:33:46 AM2/19/08
to haskel...@haskell.org
Am Sonntag, 17. Februar 2008 10:12 schrieb Colin Paul Adams:
> The mnemonics is that Right x is "right" in the sense of
> correct. So, the error case has to be Left err .
>
> As I said, this is sinister (i.e. regarding left-handed people as
> evil).

I hardly can believe that you mean this seriously. Do you really think that
the Haskell architects wanted to offend left-handed people? What does assure
you that the names of the Either constructors are about handedness? Are you
really so sensitive that you want to make people think about all kinds of
misinterpretations the usage of an everyday word may cause before they use
it? I’d propose that people don’t search for non-existent defamation so that
productivity doesn’t get buried under the search for “politically correct”
words.

Actually, I wouldn’t have dreamed of Left being related to left-handedness.
To me, it has long been very clear that Left and Right were assigned its
meaning this way round because otherwise you wouldn’t get Functor and Monad
instances. A pure technical reason, having nothing to do with hands,
politics and whatever you might think of.

> […]

Best wishes,
Wolfgang

Cale Gibbard

unread,
Feb 20, 2008, 4:58:48 AM2/20/08
to Alan Carter, Haskell-cafe Mailing List
(I'm copying the list on this, since my reply contains a tutorial
which might be of use to other beginners.)

On 19/02/2008, Alan Carter <alang...@gmail.com> wrote:
> Hi Cale,
>
> On Feb 19, 2008 3:48 PM, Cale Gibbard <cgib...@gmail.com> wrote:
> > Just checking up, since you haven't replied on the list. Was my
> > information useful? Did I miss any questions you might have had? If
> > you'd like, I posted some examples of using catch here:
>
> Thanks for your enquiry! My experiment continues. I did put a progress
> report on the list - your examples together with a similar long an
> short pair got me over the file opening problem, and taught me some
> things about active whitespace :-) I couldn't get withFile working
> (says out of scope, maybe 'cos I'm ghc 6.6 on my Mac)

Make sure to put:

import System.IO

at the top of your source file, if you haven't been. This should
import everything documented here:
http://www.haskell.org/ghc/docs/latest/html/libraries/base/System-IO.html

> but it turned out the line I was looking for (collapsed from the examples)
> was:
>
> text <- readFile "data.txt" `catch` \_ -> return ""
>
> This ensures the program never loses control, crashing or becoming
> unpredictable by attempting to use an invalid resource, by yielding an
> empty String if for any reason the file read fails. Then an empty
> String makes it very quickly through parsing. I guess that's quite
> "functiony" :-)
>
> Amazing how easy once I knew how. Even stranger that I couldn't find a
> "bread and butter" example of it.
>
> Then I was going very quickly for a while. My file is dumped from a
> WordPress MySql table. Well formed lines have 4 tab separated fields
> (I'm using pipes for tabs here):
>
> line id | record id | property | value
>
> Line IDs are unique and don't matter. All lines with the same record
> ID give a value to a property in the same record, similar to this:
>
> 1|1|name|arthur
> 2|1|quest|seek holy grail
> 3|1|colour|blue
> 4|2|name|robin
> 5|2|quest|run away
> 6|2|colour|yellow
>
> Organizing that was a joy. It took minutes:

let cutUp = tail (filter (\fields -> (length fields) == 4)
(map (\x -> split x '\t') (lines text)))

This should almost certainly be a function of text:

cutUp text = tail (filter (\fields -> (length fields) == 4)
(map (\x -> split x '\t') (lines text)))

> I found a split on someone's blog (looking for a library tokenizer),
> but I can understand it just fine. I even get to chuck out ill-formed
> lines and remove the very first (which contains MySql column names) on
> the way through!

Sadly, there's no general library function for doing this. We have
words and lines (and words would work here, if your fields never have
spaces), but nobody's bothered to put anything more general for simple
splitting into the base libraries (though I'm sure there's plenty on
hackage -- MissingH has a Data.String.Utils module which contains
split and a bunch of others, for example). However, for anything more
complicated, there are also libraries like Parsec, which are generally
really effective, so I highly recommend looking at that at some point.

> I then made a record to put things in, and wrote some lines to play
> with it (these are the real property names):
>
> data Entry = Entry
> { occupation :: String
> , iEnjoyMyJob :: Int
> , myJobIsWellDefined :: Int
> , myCoworkersAreCooperative :: Int
> , myWorkplaceIsStressful :: Int
> , myJobIsStressful :: Int
> , moraleIsGoodWhereIWork :: Int
> , iGetFrustratedAtWork :: Int
> }
> ...
> let e = Entry{occupation = "", iEnjoyMyJob = 0}
> let f = e {occupation = "alan"}
> let g = f {iEnjoyMyJob = 47}
> putStrLn ((occupation g) ++ " " ++ (show (iEnjoyMyJob g)))
>
> Then I ran into another quagmire. I think I have to use Data.Map to
> build a collection of records keyed by record id, and fill them in by
> working through the list of 4 item lists called cutUp. As with the
> file opening problem I can find a few examples that convert a list of
> tuples to a Data.Map, one to one. I found a very complex example that
> convinced me a map from Int to a record is possible, but gave me no
> understanding of how to do it. I spent a while trying to use foldl
> before I decided it can't be appropriate (I need to pass more values).
> So I tried a couple of recursive functions, something like:
>
> type Entries = M.Map Int Entry
> ...
> let entries = loadEntries cutUp
> ...
> loadEntries :: [[String]] -> Entries
> loadEntries [] = M.empty Entries
> loadEntries [x : xs] = loadEntry (loadEntries xs) x
-- Possible common beginner error here: [x:xs] means the list with one
element which is a list whose first element is x and whose tail is xs.
Your type signature and the type of cutUp seems to confirm that this
is the right type, but you don't seem to have a case to handle a
longer list of lists. If you want just a list with first entry x, and
with tail xs, that's just (x:xs). If you want to handle lists of lists
recursively, you'll generally need two cases: ([]:xss) and
((x:xs):xss). We'll end up doing something different instead of
recursion in a moment.
>
> loadEntry entries _ rid fld val = entries
>
> Trying to create an empty map at the bottom of the recursion so later
> I can try to fiddle about checking if the key is present and crating a
> new record otherwise, then updating the record with a changed one (a
> big case would be needed deep in to do each property update). If I'm
> on the right track it's not good enough to get better, so now I'm just
> throwing bits of forest animals into the pot at random again :-(
>
> So I certainly would be grateful for a clue! The bits I can do (I got
> a non-trivial wxHaskell frame sorted out quite easily, the tokenizing
> and record bit were OK) I think show I'm not *totally* stupid at this,
> I'm putting loads of time investment in (it's an experiement in
> itself) but there do seem to be certain specific things that would be
> ubiquitous patterns in any production or scripting environment, which
> are not discussed at all and far from obvious. The more I see of
> Haskell the more I suspect this issue is the gating one for popular
> uptake.
>
> I couldn't help thinking of this bit, from the Wikipedia entry on the
> Cocteau Twins:
>
> "The band's seventh LP, Four-Calendar Café, was released in late 1993.
> It was a departure from the heavily-processed, complex and layered
> sounds of Blue Bell Knoll and Heaven or Las Vegas, featuring clearer
> and more minimalistic arrangements. This, along with the record's
> unusually comprehensible lyrics, led to mixed reviews for the album:
> Some critics accused the group of selling out and producing an
> 'accessible album,' while others praised the new direction as a
> felicitous development worthy of comparison with Heaven or Las Vegas."
>
> Best wishes,
>
> Alan

I woke up rather early, and haven't much to do, so I'll turn this into
a tutorial. :)

Okay. The most common ways to build a map are by using the fromList,
fromListWith, or fromListWithKey functions. You can see them in the
documentation here:

http://www.haskell.org/ghc/docs/latest/html/libraries/containers/Data-Map.html#v%3AfromList

The types are:

fromList :: (Ord k) => [(k,a)] -> Map k a

fromListWith :: (Ord k) => (a -> a -> a) -> [(k,a)] -> Map k a

fromListWithKey :: (Ord k) => (k -> a -> a -> a) -> [(k,a)] -> Map k a

They take a list of (key,value) pairs, and build a map from it.
Additionally, the fromListWith function takes a function which
specifies how the values should be combined if their keys collide.
There is also a fromListWithKey function which allows the means of
combination to depend on the key as well.

At this point we realise something interesting about the way the data
is being represented: if there is a field in someone's record with no
row in the database, what should the resulting field contain? In C,
they often use some integer which is out of range, like -1 for this.

How about for a missing occupation field? Well, that's a String, you
could use some generic failure string, or an empty string, but I'll
show you another possibility that just might be convenient.

If t is any type, then the type (Maybe t) consists of the values
Nothing, and Just x, whenever x is a value of type t. This is another
convenient way to represent the idea that a computation might fail.

Let's start by changing your record type so that each field is a Maybe
value, that is, either the value Nothing, or the value Just x, where x
is the value it would have been.

data Entry = Entry
{ occupation :: Maybe String
, iEnjoyMyJob :: Maybe Int
, myJobIsWellDefined :: Maybe Int
, myCoworkersAreCooperative :: Maybe Int
, myWorkplaceIsStressful :: Maybe Int
, myJobIsStressful :: Maybe Int
, moraleIsGoodWhereIWork :: Maybe Int
, iGetFrustratedAtWork :: Maybe Int
}

There's a very general function in the module Control.Monad which I'd
like to use just for the Maybe type here. It's called mplus, and for
Maybe, it works like this:

mplus (Just x) _ = Just x
mplus Nothing y = y

So if the first parameter isn't Nothing, that's what you get,
otherwise, you get the second parameter. Of course, this operation has
an identity element which is Nothing.

So this lets you combine partial information expressed by Maybe types,
in a left-biased way.

It's about to become obvious that record types are less convenient
than perhaps they could be in Haskell, and this is absolutely true --
I'd actually probably use a somewhat different representation myself
(possibly something involving a Map from Strings (field names) to Int
values), but I can't really be sure what you intend with this data,
and how much type safety you want.

I'll elide the field names just because I can here. It's not
necessarily good style.

combine :: Entry -> Entry -> Entry
combine (Entry a1 a2 a3 a4 a5 a6 a7 a8) (Entry b1 b2 b3 b4 b5 b6 b7 b8)
= Entry (a1 `mplus` b1) (a2 `mplus` b2) (a3 `mplus` b3) (a4 `mplus` b4)
(a5 `mplus` b5) (a6 `mplus` b6) (a7 `mplus` b7) (a8 `mplus` b8)

Even with all the shorthand, this is pretty ugly (and I'll show how
I'd represent the data in a moment), but what this does is to combine
two partial entries, favouring the information in the
first, but filling the holes in the first with data from the second.
This operation has an identity element, which is:

emptyEntry = Entry Nothing Nothing Nothing Nothing Nothing Nothing
Nothing Nothing

Let's try a different representation, which is a little more flexible,
but expresses less in the type system.

data Entry = Entry { occupation :: Maybe String, survey :: M.Map String Int }
deriving (Eq, Ord, Show)

So now, instead of a bunch of separate Maybe Int fields, we have just
one Map from String to Int. If we don't have information for a field,
we simply won't have that key in the Map. Of course, this means we'll
have to use strings for field labels. If that seems unhappy, you could
always define a type like:

data SurveyQuestion = IEnjoyMyJob
| MyJobIsWellDefined
| MyCoworkersAreCooperative
| MyWorkplaceIsStressful
| MyJobIsStressful
| MoraleIsGoodWhereIWork
| IGetFrustratedAtWork
deriving (Eq, Ord, Show)

to be used in place of the String type.

Let's see how combine will look now:

combine :: Entry -> Entry -> Entry
combine (Entry o1 s1) (Entry o2 s2) = Entry (o1 `mplus` o2) (s1 `M.union` s2)

Or, using the record syntax more:

combine :: Entry -> Entry -> Entry
combine e1 e2 = Entry { occupation = (occupation e1 `mplus` occupation e2),
survey = (survey e1 `M.union` survey e2) }

Again, this new version has an identity with respect to combine, which is:

emptyEntry = Entry {occupation = Nothing, survey = (M.empty)}

Now, we just need a way to take one of your rows, and turn it into a
(key,value) pair, where the value is a partial entry.

readRow :: [String] -> (Int, Entry)
readRow [n, k, "occupation", v] = (read k, emptyEntry { occupation = Just v })
readRow [n, k, f, v] = (read k, emptyEntry { survey = M.singleton f (read v) })
readRow xs = error "failure case, should never happen!"

There is actually a failure case that I'm not handling here, which is
what happens when the value or key fails to parse as an Int. For that
we'd use reads instead of read, but let's ignore it for now.

We can then map this function over our cut up rows, something along
the lines of:

map readRow (cutUp text)

at which point we'll have a list of (Int, Entry) pairs.

We then want to fill up our Entries Map with those, and we want to
combine them as we go using the combine function:

entryMap text = M.fromListWith combine (map readRow (cutUp text))

Some final changes we could consider would be putting more of the
error handling into readRow itself: if it was to return a singleton
Map rather than an (Int, Entry) pair, then it could return the empty
Map on failure, and the results would then be combined using the
function M.unionsWith combine. We could move the length 4 test out of
cutUp then, and just make it the fall-through case in readRow. I'll
also use reads, which returns a list of (parse,rest-of-string) pairs,
to handle the failure cases where the numbers don't parse, by just
treating those rows as nonexistent:

readRow :: [String] -> M.Map Int Entry
readRow [n, k, f, v] =
case reads k of
[] -> M.empty -- the key didn't parse
(k',_):_ ->
if f == "occupation"
then M.singleton k' (emptyEntry { occupation = Just v })
else case reads v of
[] -> M.empty -- the value didn't parse
(v',_):_ -> M.singleton k'
(emptyEntry { survey = M.singleton f v' })
readRow xs = M.empty -- this handles the case when the list is any length but 4

cutUp text = tail (map (\x -> split x '\t') (lines text)) -- which
allows cutUp to be simpler

entryMap text = M.unionsWith combine (map readRow (cutUp text))

Anyway, I hope this tutorial gives some idea of how things progress,
and what sort of thinking is generally involved. Note that the focus
here was more on finding the right combining operations, and then
using pre-existing higher-order functions to collapse the structure,
than it was on recursion. Indeed, the final program there doesn't
contain any explicit recursion at all! This is generally something to
aim for. Using explicit recursion is generally a means of last resort.
Higher order functions on data structures are our control structures.

- Cale

Alan Carter

unread,
Feb 20, 2008, 2:34:41 PM2/20/08
to Cale Gibbard, haskel...@haskell.org
Cale,

On Feb 20, 2008 10:58 AM, Cale Gibbard <cgib...@gmail.com> wrote:
> (I'm copying the list on this, since my reply contains a tutorial
> which might be of use to other beginners.)

Thank you so much for this - I've just started playing with it so few
intelligent responses yet. I'm sure it will be of *huge* use to
others, right in the middle of the "gap" I fell into.

The experiment continues - I'll be back :-)

Many thanks,

Alan

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

Thomas Davie

unread,
Feb 22, 2008, 7:23:39 AM2/22/08
to Cale Gibbard, Haskell-cafe Mailing List
A quick note here. This is a *really* excellent tutorial on a variety
of subjects. It shows how monad operators can be used responsibly (to
clarify code, not obfuscate it), it shows how chosing a good data
structure and a good algorithm can work wonders for your code, and on
a simplistic level, it shows how to build a database in Haskell.

Would it be possible to clean this up and put it in the wiki somewhere?

Thanks

Bob

_______________________________________________

Alan Carter

unread,
Mar 4, 2008, 1:29:50 AM3/4/08
to haskel...@haskell.org
Many thanks for the explanations when I was first experimenting with
Haskell. I managed to finish translating a C++ wxWidgets program into
Haskell wxHaskell, and am certainly impressed.

I've written up some reflections on my newbie experience together with
both versions, which might be helpful to people interested in
popularizing Haskell, at:

http://the-programmers-stone.com/2008/03/04/a-first-haskell-experience/

Regards,

Alan

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

Paul Johnson

unread,
Mar 4, 2008, 5:48:58 AM3/4/08
to Alan Carter, haskel...@haskell.org
Alan Carter wrote:
> I've written up some reflections on my newbie experience together with
> both versions, which might be helpful to people interested in
> popularizing Haskell, at:
>
> http://the-programmers-stone.com/2008/03/04/a-first-haskell-experience/
>
Thank you for writing this.

On the lack of simple examples showing, for example, file IO: I seem to
recall a Perl book (maybe it was Edition 1 of the Camel Book) which had
lots of very short programs each illustrating one typical job. Also the
Wiki does have some pages of "worked example" programs. But I agree, we
could do better.

I'm surprised you found the significant whitespace difficult. Yes, the
formal rules are a bit arcane, but I just read them as "does the Right
Thing", and it generally works for me. I didn't know about the
significance of comments, but then I've never written an outdented comment.

I had a look through your code, and although I admit I haven't done the
work, I'm sure that there would be ways of factoring out all the
commonality and thereby reducing the length.

Finally, thanks for that little story about the BBC B. I had one of
those, and I always wondered about that heatsink, and the stonking big
resistor next to it. They looked out of scale with the rest of the board.

Paul.

Ketil Malde

unread,
Mar 4, 2008, 6:17:03 AM3/4/08
to haskel...@haskell.org
Paul Johnson <pa...@cogito.org.uk> writes:

> I'm surprised you found the significant whitespace difficult.

I wonder if this has something to do with the editor one uses? I use
Emacs, and just keep hitting TAB, cycling through possible alignments,
until things align sensibly. I haven't really tried, but I can
imagine lining things up manually would be more painful, especially
if mixing tabs and spaces.

-k
--
If I haven't seen further, it is by standing in the footprints of giants

Luke Palmer

unread,
Mar 4, 2008, 6:45:16 AM3/4/08
to Ketil Malde, haskel...@haskell.org
On Tue, Mar 4, 2008 at 4:16 AM, Ketil Malde <ke...@malde.org> wrote:
> Paul Johnson <pa...@cogito.org.uk> writes:
>
> > I'm surprised you found the significant whitespace difficult.
>
> I wonder if this has something to do with the editor one uses? I use
> Emacs, and just keep hitting TAB, cycling through possible alignments,
> until things align sensibly. I haven't really tried, but I can
> imagine lining things up manually would be more painful, especially
> if mixing tabs and spaces.

Especially if mixing tabs and spaces indeed. Haskell does the Python
thing of assuming that a tab is 8 spaces, which IMO is a mistake. The
sensible thing to do if you have a whitespace-sensitive language that
accepts both spaces in tabs is to make them incomparable to each
other; i.e.


main = do
<sp><sp>putStrLn $ "Hello"
<sp><sp><tab>++ "World"
-- compiles fine


main = do
<sp><sp>putStrLn $ "Hello"
<tab>++ "World"
-- error, can't tell how indented '++ "World"' is...

Luke

Paul Moore

unread,
Mar 4, 2008, 10:19:09 AM3/4/08
to Alan Carter, haskel...@haskell.org
On 04/03/2008, Alan Carter <alang...@gmail.com> wrote:
> http://the-programmers-stone.com/2008/03/04/a-first-haskell-experience/

That was an interesting read. Thanks for posting it. I also liked the
tale of the BBC ULA - it reminded me of a demo I saw once at an Acorn
show, where they had a RISC PC on show, with a (IBM) PC card in it.
They were demonstrating how hot the PC chip runs compared to the ARM
RISC chip by using it to make toast. I dread to think what you could
do with one of today's monsters :-)

Paul.

Cale Gibbard

unread,
Mar 4, 2008, 11:21:36 AM3/4/08
to Luke Palmer, haskel...@haskell.org
On 04/03/2008, Luke Palmer <lrpa...@gmail.com> wrote:
> On Tue, Mar 4, 2008 at 4:16 AM, Ketil Malde <ke...@malde.org> wrote:
> > Paul Johnson <pa...@cogito.org.uk> writes:
> >
> > > I'm surprised you found the significant whitespace difficult.
> >
> > I wonder if this has something to do with the editor one uses? I use
> > Emacs, and just keep hitting TAB, cycling through possible alignments,
> > until things align sensibly. I haven't really tried, but I can
> > imagine lining things up manually would be more painful, especially
> > if mixing tabs and spaces.
>
>
> Especially if mixing tabs and spaces indeed. Haskell does the Python
> thing of assuming that a tab is 8 spaces, which IMO is a mistake. The
> sensible thing to do if you have a whitespace-sensitive language that
> accepts both spaces in tabs is to make them incomparable to each
> other; i.e.
<snip>

I honestly think that tab characters occurring anywhere but in a
comment should be considered a lexical error and rejected by the
compiler outright. More problems are caused by trying to continue with
only tabs, or some mixture of tabs and spaces than just getting one's
editor to expand tabs automatically.

- Cale

Evan Laforge

unread,
Mar 4, 2008, 1:53:20 PM3/4/08
to Luke Palmer, haskel...@haskell.org
> Especially if mixing tabs and spaces indeed. Haskell does the Python
> thing of assuming that a tab is 8 spaces, which IMO is a mistake. The

FWIW, most people in python land think the same thing, and the -t flag
makes mixed tabs and spaces a warning or error. At the least, -Wall
could report mixed usage. At the most, make it an error.

hjg...@chello.nl

unread,
Mar 4, 2008, 5:02:15 PM3/4/08
to haskel...@haskell.org

About the line length needed for Haskell programs, there was a discussion
about this some time ago, that could be regarded as a tutorial for
reducing indentation:
http://haskell.org/pipermail/haskell-cafe/2007-July/028787.html

As for the idle core you mention: I keep one core fully occupied with a
program that searches for a cure against cancer, see:
http://www.computeagainstcancer.org/

The example you gave for the use of "map" can be simplified:
map func (take (10 [0..])) -- should actually be: map func (take 10
[0..])
->
map func [0..9]

Regards,
Henk-Jan van Tuyl


--
http://functor.bamikanarie.com
http://Van.Tuyl.eu/
--


On Tue, 04 Mar 2008 07:29:24 +0100, Alan Carter <alang...@gmail.com>
wrote:

> Many thanks for the explanations when I was first experimenting with
> Haskell. I managed to finish translating a C++ wxWidgets program into
> Haskell wxHaskell, and am certainly impressed.
>
> I've written up some reflections on my newbie experience together with
> both versions, which might be helpful to people interested in
> popularizing Haskell, at:
>
> http://the-programmers-stone.com/2008/03/04/a-first-haskell-experience/
>
> Regards,
>
> Alan
>

--
--
Met vriendelijke groet,
Henk-Jan van Tuyl


--
http://functor.bamikanarie.com
http://Van.Tuyl.eu/
--

Chaddaï Fouché

unread,
Mar 4, 2008, 5:30:21 PM3/4/08
to Alan Carter, haskel...@haskell.org
2008/3/4, Alan Carter <alang...@gmail.com>:

> I've written up some reflections on my newbie experience together with
> both versions, which might be helpful to people interested in
> popularizing Haskell, at:
>
> http://the-programmers-stone.com/2008/03/04/a-first-haskell-experience/

This is truly interesting, any learning experience is enlightening, we
truly do need to lower this barrier of admittance of which you speak.

On another subject, there are still point in your code that could be
clearer or done with less cruft :

maxOfHistogram stats = snd (foldl (\(cA, vA) (cB, vB) -> if (vA > vB)
then (cA, vA)
else (cB, vB))
(0, 0)
stats)

can become :

maxofHistogram stats = foldl' max 0 (map snd stats)

("foldl' max 0" could be replaced by "maximum" but there wouldn't be a
default 0 anymore)

more importantly, you can replace this kind of code :
vA <- varCreate []
vB <- varCreate []
-- ...
vL <- varCreate []
vM <- varCreate []
vN <- varCreate []
vO <- varCreate []

by :
[vA, vB, vC, vD, vE, vF, vG, vH, vI, vJ, vK, vL, vM, vN, vO] <-
replicateM 15 (varCreate [])

(true also for the "dA <- textEntry statusFrame [text := "0",
alignment := AlignRight]" sequence)

I'm not sure that functions like getdTotal couldn't be improved, I
wonder if a small Map for the elements of d wouldn't make the code
much better and offer other opportunities for abstractions. As it is,
enumeration like :

[[label "Total Entries", widget (getdTotal d)]
,[label "Valid Entries", widget (getdValid d)]
-- ...
,[label "MDMA", widget (getdMdma d)]
,[label "Caffeine", widget (getdCaffeine d)]]

could be slightly reduced by :
let bindLabelAndWidget (lbl,getter) = [label lbl, widget (getter d)]
in map bindLabelAndWidget [("Total Entries", getdTotal), ("Valid
Entries", getdValid)
,(...)]

And little thing like :
mapM_ (\f -> do repaint f) knownFrames
becoming :
mapM_ repaint knownFrames


I also do concur that a flag or a warning to signal mixed tabulations
and space would be a _very_ good idea !

--
Jedaď

Don Stewart

unread,
Mar 4, 2008, 5:35:30 PM3/4/08
to Chaddaï Fouché, haskel...@haskell.org
chaddai.fouche:

> 2008/3/4, Alan Carter <alang...@gmail.com>:
> > I've written up some reflections on my newbie experience together with
> > both versions, which might be helpful to people interested in
> > popularizing Haskell, at:
> >
> > http://the-programmers-stone.com/2008/03/04/a-first-haskell-experience/
>
>
> I also do concur that a flag or a warning to signal mixed tabulations
> and space would be a _very_ good idea !
>

Such a flag already exists:

-fwarn-tabs

As in:

$ ghc -fwarn-tabs A.hs -no-recomp
A.hs:3:0: Tab character

-- Don

Lennart Augustsson

unread,
Mar 5, 2008, 6:48:28 PM3/5/08
to Alan Carter, haskel...@haskell.org
Thanks for an interesting write-up. And not bad for a first Haskell
program. :)
There's still a number of things you could do to limit the boiler plate
code, though.

On Tue, Mar 4, 2008 at 6:29 AM, Alan Carter <alang...@gmail.com> wrote:

> Many thanks for the explanations when I was first experimenting with
> Haskell. I managed to finish translating a C++ wxWidgets program into
> Haskell wxHaskell, and am certainly impressed.
>
> I've written up some reflections on my newbie experience together with
> both versions, which might be helpful to people interested in
> popularizing Haskell, at:
>
> http://the-programmers-stone.com/2008/03/04/a-first-haskell-experience/
>
> Regards,
>
> Alan
>
> --

> ... the PA system was moaning unctuously, like a lady hippopotamus

0 new messages