How to Read Clojure Code Like an Expert
Summary: As you get better with Clojure, it becomes easier to read. Why not jump ahead of the learning curve and read like an expert? Focus on the first thing, use the indentation, and read the evaluation order.
One of the most frequent complaints about Clojure (and Lisps in general) is the ever-present parenthesis. Unfortunately, the stock answer is "you stop focusing on the parentheses after a few weeks". That's not very helpful for beginners. It doesn't tell the beginner what to focus on instead.
Knowing what to focus on and how to interpret what you're seeing are two skills that experts in any field have that beginners lack. Beginners see lots of parentheses. But what do experts see? What do they know that beginners don't? Here are three really important ways experts read Clojure.
1. Focus on the first thing after the parens
In Ruby code, your eyes often use punctuation to find the meaning of code. Commas, braces, brackets, hash rocket, period, and, yes, parentheses. These all influence the meanings of what's around them. And since they're not letters, it's easy to pick them out visually.
In Clojure, you do have several types of punctuation. But overwhelming all of the others are the parentheses. Lisp has been called "toenail soup", and "oatmeal with toenail clippings mixed in". Yet people who use it a lot say they don't mind the parentheses anymore. What gives?
The thing is, the parens aren't very meaningful by themselves. It only takes a little while programming in Clojure for your eyes to start looking not at the parens but at the first thing inside the paren for meaning. The first thing in the list defines how the rest of the list is interpreted.
Special forms are defined in the compiler. There are also macros, method calls, and functions. Special forms and macros all define their own rules for interpreting the rest of the stuff in the expression. You'll pick those up over time, just like you can pick up the difference between curly braces and square braces. The other thing it can be is a method or function, which follow standard rules for evaluation (evaluate the arguments first, then call the function on them).
These rules are internalized by Clojure programmers. It's not hard to learn all of the special forms and the common macros (and the rest are functions). It comes with time.
Of course, all of the different types of expresions can nest very nicely. Which brings us to the next point.
2. Indentation means scoping
Another common complaint is how much nesting is done in Lisps. It's true, there's more nesting than in other languages. When I was working in JavaScript, nesting felt bad. But in Clojure, you expect more nesting than in other languages. There is still such a thing as "too much nesting". The line is just drawn further to the right. But as a Clojure programmer, I appreciate the nesting because it helps me write better code.
Having parens play such a big role in the language means indentation is very easy and regular. There is a rather standard style for how Clojure code should be indented.^1 The result of the indentation is to let you see nesting without counting parens.
Nesting is important in Clojure. Nesting defines scopes and groups functions with their arguments. Scopes let you reason about small bits of code in isolation. Nesting visually shows you the structure of your program. After a short while, you will be able to see the important structure of the code by the indentation. And that will lead you to the next point.
3. Learn the evaluation direction
If you use a language like JavaScript, you're probably used to seeing the evaluation order of code very quickly. What I mean is, you know that you should read the following code from left to right.
person.getName().toUpperCase().trim()
But you also know that you should read function calls right to left (or "inside out").
trim(toUpperCase(getName(person)))
Function definitions are read from top to bottom:
function upper_name(person) {
var name = person.getName();
name = name.toUpperCase();
name = name.trim();
return name;
}
And assignments are kind of left to right to left. First, you define the variable. Then you run the code on the right, then assign it to the thing on the left.
var name = upper_name(person);
And of course, you can write code that combines all of those types of code. So you wind up reading each bit of code in a different way.
function x(person) {
var name = trim(person.getName().toUpperCase());
return name;
}
Clojure has all of that, too (well, everything except the left to right to left order). But the visual signals are different. Like the first point says, you should focus on the first thing in each pair of parens. Here are some examples of different reading directions in Clojure.
Right to left (inside out):
- function calls, method calls
(.trim (.toUpperCase (.getName person)))
Top to bottom:
(let [name (.getName person)
name (.toUpperCase name)
name (.trim name)]
name)
Left to right:
(-> person .getName .toUpperCase .trim)
Those are just some examples. It takes a little practice, but soon you'll be seeing the order you should read code in using the first item in the parens.
Conclusions
If you've never done Lisp before, the Clojure syntax can be a bit novel and bewildering. But it only takes a couple of weeks before you'll have your bearings again. Just remember that everyone goes through that at first. After the adjustment, though, you'll be able to easily pick out the visual landmarks you'll need. And hopefully knowing what to look for can help you through that faster.
If you're serious about learning Clojure for a career change, you should look into PurelyFunctional.tv Online Mentoring. It will guide you step-by-step to becoming a Clojure professional.
- There are slight variations.