Coding style, Haskell
I finished chapter five of the Haskell book I’m reading last night, and the bits of it are starting to make sense. I was doing some of the exercises and managing to write functions that compiled first time – I’m all about the small victories.
What I’m enjoying most about the book, though, is that as well as teaching the language it does a good job of teaching some of the culture. A lot of writing good software is about making the most effective use of the tools a language provides and that usually only comes with time and experience. The book helps, though, providing gentle prods in the right direction:
Many tail recursive functions are better expressed using list manipulation functions like map, take, and filter. Without a doubt, it takes some practice to get used to using these. What we get in return for our initial investment in learning to use these functions is the ability to skim more easily over code that uses them.
The reason for this is simple. A tail recursive function definition has the same problem as a loop in an imperative language: it’s completely general, so we have to look at the exact details of every loop, and every tail recursive function, to see what it’s really doing. In contrast, map and most other list manipulation functions do only one thing; we can take for granted what these simple building blocks do, and focus on the idea the code is trying to express, not the minute details of how it’s manipulating its inputs.
In the middle ground between tail recursive functions (with complete generality) and our toolbox of list manipulation functions (each of which does one thing) lie the folds. A fold takes more effort to understand than, say, a composition of map and filter that does the same thing, but at the same time it behaves more regularly and predictably than a tail recursive function. As a general rule, don’t use a fold if you don’t need one, but think about using one instead of a tail recursive loop if you can.
As for anonymous functions, they tend to interrupt the “flow” of reading a piece of code. It is very often as easy to write a local function definition in a let or where clause, and use that, as it is to put an anonymous function into place. The relative advantages of a named function are twofold: we’re not confronted with the need to understand the function’s definition when we’re reading the code that uses it; and a well chosen function name acts as a tiny piece of local documentation.
… and they’ve not once suggested I write any comments yet. Woot