An Opinionated Review of Clojure Applied

Why write a book about open-source software? (Not for the money. Trust me.) I’ve seen far too many “technical books” that merely regurgitate the documentation of a bunch of open source libraries. I’m happy to say that Clojure Applied, by my friends and colleagues Alex Miller and Ben Vandgrift, is not in this category. They sent me a free copy in return for review, so here it is.

As my other colleague Luke VanderHart pointed out in his recent talk at ClojureTRE, the biggest gap in written material about about Clojure — and a good candidate for the source of most “documentation” complaints — has been the lack of narrative. Clojure has great reference documentation, and lots of Clojure libraries come with great tutorials, but there aren’t many comprehensive stories about building complete applications.

Clojure Applied tells a story about how to write Clojure applications. This is not just a book about Clojure, it’s a book about how to write software which assumes you’re going to use Clojure to do it. Clojure Applied would probably not be a good choice as your first Clojure book, although it would be an excellent book to read while you are learning Clojure.

The narrative develops like most successful Clojure programs: from the bottom up, starting with data. Instead of primitives or abstract concepts, the first chapter begins with modeling domain data using maps and records. This is the right place to start, and I could wish this chapter went into even more detail. For example, I would have liked to see a comparison of nested versus “flat” map structures (“flatter” is easier to work with).

Domain modeling is a difficult concept to describe, so I can’t fairly criticize Ben & Alex’s efforts here, but I do think they lose their way slightly by introducing Prismatic’s Schema library very early on. To be sure, Schema is a powerful library that a lot of Clojure developers find useful. But placed here, along with discussions of type-based dispatch, it leaves the reader with the idea that types are a central feature of domain modeling in Clojure. I disagree. Thinking in terms of types early in the design process often leads to unnecessarily restrictive choices, producing inflexible designs.

Further compounding the error of focusing on types, this chapter wanders into more advanced topics such as protocols and multimethods. There’s even a technique for dynamically extending protocols at runtime, an advanced tactic I would not recommend under most circumstances.

Embracing the possibilities of design in a dynamically-typed language requires a willingness to work with values whose types may be unknown at certain points. For example, the type of the “accumulator” value in a transducer is hard to define, because for most transducers its type is irrelevant. The ability to ignore types when they are not needed is what makes dynamic languages so expressive.

On the other hand, I have seen many large Clojure programs drift into incomprehensibility by failing to constrain their types in any way, passing complex nested structures everywhere. In this case, the lack of validation leads to a kind of inside-out spaghetti code in which it’s impossible to deduce the type of something by reading the code which uses it. Given the choice between these two extremes, “over-typed” code will be easier to untangle than “under-typed,” so perhaps introducing validation early is a good idea.

Moving along, Chapter Two covers the other Clojure collection types. This is beginner material, but presented in terms of how and why you might want to use each type of collection. This chapter also covers some common beginner questions, such as how to search a sequential collection. Another advanced topic which I would not recommend — defining a new collection type implementing Clojure’s interfaces — sneaks in here, but I’ll give it a pass because it helps you understand the collection interfaces.

Chapter Three zeroes in on the sequential collections, in particular the sequence library. Here the narrative is about combining sequence functions, in particular the pattern of filter-map-reduce. This pattern is so fundamental that an experienced Clojure programmer (or Lisp, or any functional language) might not even think about it, but it’s a critical step to becoming an effective user of Clojure. This chapter also introduces Transducers. Even though Transducers might be considered an “advanced” topic, I think they belong here alongside sequences. The concepts are the same, and Transducers are really quite straightforward if you’re only looking, as this chapter does, at how to use them and not how they are implemented.

Part II, “Applications,” is probably the best section in the book. This is the critical piece missing from the first-round books about Clojure (including my own). How do you start combining all these little functional pieces into a working program?

The first chapter in this section describes mutable references with the most comprehensible real-world example I have seen, and also includes an excellent explanation of identity versus state. Another chapter describes all the various techniques for using multiple cores, including an important discussion of pipelines and core.async go blocks as processes.

Then there’s an entire chapter devoted to components as they are expressed in namespaces. Ben & Alex introduce all the concepts necessary to design and implement components without using my Component library, which I think is a smart choice. The Component library arrives in the following chapter, along with the idea of composing an application out of many components.

Part III, “Practices,” covers testing, output formats, and deployment.

The chapter on testing has a good description of the trade-offs between example-based and property-based testing, but doesn’t delve into the more difficult areas of integration or whole-system testing. Advanced testing techniques really deserve an entire book of their own.

“Formatting data” covers the usual suspects: JSON, EDN, and Transit. In my experience, the choice of data format is usually dictated by external constraints, but this chapter at least makes the trade-offs clear.

Finally, the chapter on deployment is a high-level overview of everything from GitHub to Elastic Beanstalk. There’s even a discussion of open-source licensing and contributor agreements. Heroku gets the most attention, which makes sense for a book targeted at mostly-beginners, but at least this chapter introduces some of the concerns one might want to think about when choosing a deployment platform.

After the last chapter, there’s a bonus pair of appendices. The first briefly covers the “roots” of Clojure, with links to source material. The second summarizes some principal motivations behind Clojure’s design as a guide to “Thinking in Clojure.” This latter section might have been more usefully incorporated into the text of the book, but that’s harder to write and can tend toward the preachy, so I can’t complain.

Circling back to where I started, Clojure Applied is a great book to read while learning Clojure. It’s not a language tutorial. It’s not stuffed with revolutionary ideas. Most importantly, it doesn’t try to do too much. It’s just solid, practical advice. Even the recommendations I disagree with are not bad ideas, just different preferences. Follow Ben & Alex’s advice while building your first Clojure program, and you’ll have a solid foundation to explore your own ideas and preferences.