So, I'm an Idiot. How not to Learn Haskell

I’ve just come to the realization that two months of haskell progress in my regatta scoring programme was a waste of time. I was porting from python so I couldn’t escape from the imperative mindest. My problem was I needed to analyze some racing results and compute results progressively proceeding from raw data, through a sucession of refinements, until finally being able to compute overall results. This was too messy to hand around piece by piece so I needed an overall record to contain the data as it was computed. This was my downfall.

Because I was generating the data progressively, later computations depending on earlier ones, I was assuming that to define the overall computation in a purely functional way the declarative form would have to mirror the data flow. I saw myself as having to pass a record with undefined fields from function to function, adding fields and passing the more defined record onto later computations. Consequently I decided I would have to use a monadic approach to emulate the imperative style. This would avoid the unecessary duplication and bookkeeping of the purer approach.

To this end I had progressed along quite a way. I had mastered ST monads to allow me to run the imperative code anywhere in a functional setting. The ReaderT monad transformer allowed me to pass my record as a kind of stack frame (or object frame or whatever) through the whole sequence of computations implicitly. I came up with a FramingFunctor class which took a monad as a parameter so I could define both a frozen and thawed version of the same record (and map between them) for use during and after the runST. The thawed version wrapped all fields in a (STRef s) monad and the frozen used simply an Identity monad. It was somewhat baroque but only took a few (dense) lines of code to create the mapping instances for thawing and freezing.

I was recapitulating a procedural programming language from the ground up and I’m an idiot. While walking today I thought how nice it would be if the lazy nature of haskell would allow my undefined thunks to become defined on demand; that would obviate the need for all this runST nonsense altogether. Of course I knew it was impossible. You have to define thunks up front when you define the record and in the overall dataflow of the programme those fields weren’t yet ready to be defined, depending as they did on other fields. And then it hit me. Haskell does recursive definition through those thunks to solve precisely that problem. Simply by defining my record recursively, haskell would have automatically handled all the bookeeping by itself without any further effort on my part. Sure, I would have to return all the defined values to the topmost layer of definition for the record, instead of hiding writeSTRefs throughout and using computations that usually just returned (), but that’s a small price indeed.

Recursive definition of data breaks the links between data flow and lexical layout (control flow?) even in a purely functional setting. It isn’t necessary to build an imperative language on top of a functional foundation just to avoid dataflow bookkeeping. Thinking functional === dataflow was my preconceptions about what functional code had to look like and it is just wrong.

I’m four months into learning haskell and only now do I realize I missed a large chunk of the fundamentals up front.