|P is for Practical|
Like I wrote in my earlier post, I almost always work top down. So, to take up your challenge, I will start with a top-level outline: read in the inputs, process them somehow, and then show the results. Since I don't know what it means (yet) to perform each of those tasks, I will use simple placeholders like id to represent each stage of the outline:
This is legal Haskell that compiles and executes. It doesn't do much, however, merely echoing its input:
$ echo alpha beta | ghc -e main WordIndex.hs alpha betaStill, it shows how easy it is to start sketching a solution at a high level.
Next, working down, I will flesh out the outline. I won't worry about reading input from files or writing output to an index just yet. For now, I will focus on the core word-counting problem, adding logic to do just that:
Notice that I did not need to alter the outline. All I did was add low-level detail to it. Now I have counts:
$ echo alpha beta | ghc -e main WordIndex.hs alpha 1 beta 1
Continuing to refine the lower-level functionality, I will return to the I/O tasks. Like before, I will add lower-level functions, leaving the top-level outline largely unchanged:
Let me recap. I started with a top-level, executable outline and worked down from there. With two simple iterations I was able to add the counting functionality and then the I/O functionality. Now I have a complete solution that is largely equivalent to your code (ignoring details that don't matter for our top-down coding discussion).
As a demonstration, I will compile the code and use it to index itself:
$ ghc -O2 --make -o WordIndex WordIndex.hs $ ./WordIndex *.hs $ cat words.index "words.index" 1 $ 1 (+) 1 (main) 1 (unwords 1 . 5 1 1 :) 1 = 9 >>= 2 Control.Monad 1 Data.Map 1 Main 1 Map 1 Map.empty 1 ...
There you have it: top-down functional programming, or at least the way I do it in Haskell. I hope you will trust me that it was quick and easy. (It took much longer to write this explanation than the code.)