
andrewcoppin:
I could try GHC's new debugger. But my experiences with it so far have shown that for all but the most trivial programs possible, it becomes intractably difficult to figure out what the debugger is actually showing you. I actually tried to debug a "normal" LZW implementation once - one that didn't involve two highly convoluted custom monads and a stateful execution model with manually threaded state. This is *not* my idea of a fun time...
For what its worth, people are using this at work on large projects happily.
In a "normal" programming language, at this point you would sprinkle a few print statements around so you can see the intermediate steps the program is taking. But I can't. I'm in the wrong monad. Curses!
Use Debug.Trace.trace.
In the end, the only solution I could come up with was to design a second, "hacked" version of the two monads. Instead of importing one module, you import another that provides the same interface. However, now Reader and Writer are aliases to IO. All write requests cause pretty-printed binary to be sent to stdout, and all read requests pop up a prompt for input from stdin. It worked reasonably well in that I could now add the vitally necessary print statements, and see intermediate values and so forth... It wasn't very pretty though.
You should have used Debug.Trace.
So at least I got that part fixed. Heh. But now I find myself worrying about yet *another* problem: is Writer lazy enough?
I mean, the idea is that you can write a program that lazily reads from a file, pushes it through a Writer, and lazily writes the result back to another file. The thing should chug along reasonably fast and use a constant amount of RAM. But all this is only true of Writer is actually lazy enough. I have a horrible feeling that all the complicated Origami inside makes it too strict. And I have no way to check!
Actually, you can use 'chasingBottoms' to write QuickCheck properties that state your expected laziness or strictness behaviour.
Actually, thinking about it, is Reader lazy enough? You call run_reader and it hands over your data, but if that data is, say, a list, will run_reader build the entire damn list before it'll hand it over? Or will the monadic code by called as-needed to generate the list elements? Obviously I desire the latter, but I'm really not sure what the actual behaviour is...
The mtl Reader class is lazy.
In summary, I've spent several days coding, and I still don't have a single algorithm working. I've spent all my time wrestling with the mundane details of infrastructure rather than the interesting algorithms I actually set out to implement. This makes me very sad. :-(
You should have stopped by #haskell a few days ago.
If anybody can think of a better set of abstractions or another way to do debugging, I'd be interested to hear about it.
There's already a bit layer for Data.Binary on hackage, for what its worth. And an LZW encoder/decoder.
(This would all be so trivial in an OO language. Just make an Encoder object that updates its own state internally and talks to a Source object and a Destination object for its data...)
Make an Encoder class that updates its own state internally and lazy streams input and output. As Data.Binary does, as zlib does, et al. -- Don