FPIC Archive 5: Style

{:deps {org.clojure/clojure {:mvn/version "1.10.1"}
        ;; complient is used for autocompletion
        ;; add your libs here (and restart the runtime to pick up changes)
        compliment/compliment {:mvn/version "0.3.9"}}}
deps.edn
Extensible Data Notation
{:hello (clojure-version)}
0.1s
Clojure

Any customer can have a car painted any colour that he wants so long as it is black. Henry Ford

An important feature of the code you write is its readability. Code is meant for people to read and only secondarily for computers to execute.

In this chapter, we go over the idiomatic Clojure style of writing programs: indentation, whitespace, names, and other things that affect the format of the program.

Indentation

Clojure programs are indented with spaces (not tabs), and the level of indentation depends on the form.

There are three different syntax forms that have different indentation styles.

Function calls

Most forms in Clojure programs are function calls. The parameters of function calls can be indented in two ways.

If you have multiple parameters, and they are too long or too many to put on the same line, you can align them:

(and (flush? hand) (straight? hand)) (defn grandmother? [person grandmother] (or (= grandmother (mother (mother person))) (= grandmother (mother (father person)))))

In some cases, you want to put the first parameter on its own line:

(and (flush? hand) (straight? hand))

In this case, the parameters are aligned and indented with either one or two spaces. Vim indents the parameters with two spaces and Emacs with one. Either style is fine.

Literal maps, vectors, sets and lists

The elements of a literal vector are aligned:

["foo" "bar" 42 :quux (+ 1 2)]

Note that the elements on successive lines are not aligned to the opening [, but to the first element.

Maps, sets and lists are written in similarly:

{:foo 42 :bar "quux"} '("foo" "bar" 42 :quux)

In a map the key and value are usually on the same line, but if they need to be on separate lines, they are aligned:

{:foo 42 :bar "quux"}

Definitions

The body of a definition is indented with two spaces:

(defn double [x] (+ x x))

If the parameters of a function definition are on separate lines, they are aligned, like literal vectors:

(defn foo [a-really-long-parameter-name another-long-parameter-name] (body))

If a definition has a documentation string, it is written on the next line after the definition name, indented with two spaces:

(defn foo "This is the documentation string for foo. It can span multiple lines." [parameters] (body))

In this case, the parameter vector is written on its own line and indented with two spaces, like the body.

let is indented very much like a function definition. The defined names are indented and aligned like a vector literal and the body is indented with two spaces:

(let [name "Lila Black" ; ← name definitions here occupation "Magical Cyborg"] ; ← and here (str "I am " name ". " ; ← body here, indented two "Prepare to be " occupation "ed.")) ; spaces more than the let

Here the aim is to make it visually clear where the name definitions are and where the body is, so the body is not aligned with the definitions.

As a somewhat special case, if is indented like definitions:

(if (= 3 4) "maths is broked" "all is fine")

The conditional is usually on the same line as the if, and the clause bodies are indented with two spaces.

Names

Names in Clojure follow traditional Lisp style, which allows much more varied names than languages with infix syntax. No capital letters are used.

If a name has many words, they are separated with a hyphen -magical-poniesbook-authors.

Add a ? to the end of predicates (functions returning booleans): contains?nil?pony?.

Function name should reflect the result value, not the computation done: radius instead of calculate-radiusdead-authors instead of remove-living-authorsauthor-descriptions instead of describe-authors.

Functions that create objects of certain type have names of the form ->object-type->triangle->employee.

Functions that turn objects of one type to another type have names of the form from-type->to-typeauthors->string, `

Whitespace

Whitespace acts as a token separator in Clojure. That is, (+12) is parsed as a call to a function named +12[123] is a list containing the number 123 and {:a 1,:b 2} maps :a with 1,:b and 2 has no pair.

Parentheses, brackets and curlies hug their contents:

{:a 42, :b "foo"} [1 2 3 4] (foo bar)

Those pesky parentheses

When closing an expression, put all the closing parentheses (and brackets/curlies) next to each other:

(fofoaos (falidsjasd (falksjdlasd (fjldiasjd)))) ; ← All hugging each other!

Example

As an example of these rules, here is a Leiningen project definition:

(defproject blorg "0.1.0-SNAPSHOT" :dependencies [[org.clojure/clojure "1.4.0"] [noir "1.3.0-beta7"] [cssgen "0.3.0-alpha1"] [cheshire "4.0.0"]] :profiles {:test {:dependencies [[clj-webdriver "0.6.0-alpha8"] [midje "1.4.0"]]} :dev {:plugins [[lein-midje "2.0.0-SNAPSHOT"]]}} :main blorg.core)

Idioms

If a function is only needed inside another function, define it with let and fn inside the using function.

Use let liberally to give intermediate results a name. If an expression appers in two places, give it a name with let. And even if it doesn’t, using let to name intermediate results makes code easier to read.

That’s All

Now it’s time to apply these conventions in a bigger project.

Move on to the world of poker →

Runtimes (1)