Haskell Programming from First Principles cover

Haskell Programming from First Principles by Chris Allen and Julie Moronuki, better known as The Haskell Book, is a massive book. My copy runs around 1,200 pages, and I worked through all of it, exercises and all, minus a few at the very end. This was actually my second attempt. The first time I fell off somewhere around page 300, since the pace was slow and nothing in the book had me building things, so I lost momentum. This time I paired it with Learn Haskell by Example for projects and Learn You a Haskell as light reading on the side, and that combination is what got me through, with The Haskell Book building up the theory and Learn Haskell by Example giving me a wide variety of places to apply it.

The explanations are excellent, if somewhat longwinded, and the book is really good at taking complicated concepts and making them understandable. It’s slow, and in the early chapters on types and type classes it’s almost mind-numbingly slow, since those concepts are fairly straightforward, but the slow style pays off once you hit the stuff that’s actually hard. Folding is a good example. Most books show you how to sum a list with a fold, maybe implement map and filter with one, and call folding a learned concept. The Haskell Book gives it 40 solid pages, which is kind of a big deal if you come from a language where you’d normally just reach for a while loop.

The type class chapters were the best part of the book for me. Semigroup, Monoid, Functor, Applicative, Monad, Foldable, and Traversable each get a full treatment, laws included, and you write a lot of instances yourself. It was the first resource I’d read that pressed hard on the monoidal part of Applicative. Advanced Functional Programming with Elixir had already shown me ap collecting all your validation errors instead of stopping at the first one, but it never pointed out that the collecting comes from the monoidal side of Applicative, combining the structures themselves rather than just applying functions inside them, so I didn’t really understand why it worked. That’s the piece that made Validation finally make sense to me.

By the end, “follow the types” had become my main problem-solving strategy. Most of the time, once I figured out what the types required, there was only one sensible thing I could write that would compile. It sometimes felt a bit like quantum mechanics, where I could trust the equations the same way I trust the types, even when I wasn’t totally sure what it all meant. The book also cites research papers, which is unusual for an introductory programming book (Head First Go sure didn’t point me at any current research). It’s a quirk of the Haskell community, and while I’m not sure I’ll ever make it through Wadler ‘89, I appreciated the pointers.

There are a lot of exercises, more than in any other programming book I’ve read. They normally reach a little past what the chapter covered, but I could work through everything except maybe a challenge problem at the end without needing help. Some were just typing practice, since once you’ve written a Semigroup instance for Two a b, Three a b c and Four a b c d don’t teach you anything new. Others were genuinely hard, like writing a Semigroup for a type that wraps a function, which took my whole reading session.

My biggest complaint is that, for all the time it spends explaining concepts, the book is pretty light on using them in anger. I came out of the Monad and Transformers chapters knowing how to build a monad or a transformer from scratch, but without much sense of how to use them in a real program. I even had to write StateT myself as an exercise, supposedly the most obnoxious of the standard transformers, without ever seeing much of it in action, so Learn Haskell by Example is where I’m going to get that practice.

Testing follows a similar pattern. The testing chapter is solid, it covers hspec and QuickCheck, explains generators well (including the difference between product and sum types), and points you to a real project to see tests in the wild, and the book goes on to reuse QuickCheck and checkers to test the type class laws, which is great. But it mostly used tests to show off checkers for testing laws and never really embraced testing ordinary, run-of-the-mill code. There were no tests for roughly the last ten chapters, and none in the final project, and it isn’t much of a project without tests.

The Parsers chapter was the big outlier. I understood all of the material as it was presented, it just wasn’t an adequate build-up to the exercises: “Here’s how to parse a char. Cool, now figure out a log file.” It used <|> in an exercise several pages before introducing it, told you to “try try” without explaining it, and introduced tokenization mostly through examples of doing it wrong. Then it asked for a SemVer parser, IPv4 and IPv6 parsers, and a parser for Graphviz’s DOT language, and that chapter took me about a week.

Quality control seems to slip a bit as you go, too. Somewhere past page 900 the explanations got thinner. The JSON section switched examples halfway through and barely spent time on either, so I’ve seen aeson parse JSON, but I wouldn’t say I’m ready to use it. The exceptions chapter was all toy examples, never mentioned bracket or finally, and used catching SomeException as an example. The Composing Types chapter opened with a “warmup” that had me write the Applicative instance for Compose, and that was anything but a warmup. I also found a handful of errata. The State chapter has runState f >> f 0 where it needs parentheses. The book claims zipWith is just liftA2 specialized to lists, then immediately shows them producing different results. One Applicative exercise looks like it’s setting you up to produce Just 11 but can only ever produce Just 5. And it has you name constructors Success and Failure right after telling you to import QuickCheck, which already exports both.

The book is about ten years old, and you’ll feel it. The original project files pin GHC 8.6.5, which doesn’t even exist for Apple Silicon, the checkers tuple shapes for testing Traversable had changed, an old version of trifecta wanted a decade-old set of dependencies, and the final project uses network functions that have since been renamed, so I spent a fair amount of time fighting dependencies. I don’t hold version drift against a ten-year-old book, though, just expect to update some resolvers and function names as you go. The Basic Libraries chapter was like a museum trip, but most of the exhibits are still alive.

The final project is a finger daemon backed by SQLite. Honestly, it didn’t feel like much of a final project, since I mostly typed in the code from the book, but most of the prose explaining it felt unnecessary, which tells me the book did a good job preparing me, and it could have just been a code listing. The exercises at the end go from “use the sqlite3 CLI to add a user” to “build a control socket on another port while the server runs,” which is basically “go read Marlow’s book on concurrency and come back to this project.” I skipped straight to the last step: celebrate completing this massive book.

Overall, The Haskell Book is the best resource I’ve found for actually understanding Haskell. If you want to know why Applicative works the way it does, or what the Functor laws are for, this is the book. Just don’t expect it to teach you to build things. It’s light on putting its concepts to work, it drops testing for the last third, and the later chapters have a lot less polish than the early ones, so I wouldn’t have finished it without a project book alongside it. With one, it was well worth the 1,200 pages.

Full book notes.