Clojure: Common beginner mistakes (WIP)
- Style
- Namespace declaration
- State
- Functions
- Use
recur
for recursive calls to avoid blowing the stack - Prefer higher-level sequence functions over low-level recursion
- Prefer
=
over==
- Use functions directly in map/filter/…
- Trivialities
- Not embracing the platform
- Additional resources
Common mistakes made by Clojure beginners and style recommendations.
Style
Naming
Use kebab-case-names, not camelCase or something else (unless you have some interop reasons)
End predicates with
?
, i.e.isAlien
→alien?
Don’t add unnecessary prefixes to function names, such as
get-
(this comes likely from the Java Bean standard and has no place here) orcompute-
(most functions compute something!). Usedigits
for fn that turns a number into a sequence of digits,armstrong-num
for fn that computes the Armstrong Number, andarmstrong?
for fn that tells you whether a number is an Armstrong number. See also Stuart Sierra’s great How to Name Clojure Functions.
Namespace declaration
Use (ns ... (:require ...))
instead of (require ...)
or (use ...)
. Only exceptionally do :refer :all
and only use :refer [..]
for frequently used symbols, i.e. prefer using an alias (e.g. [clojure.set :as set]
) - typically the last part of the namespace name or a well-established abbreviation.
(require
is intended for use in the REPL, not in namespace files.)
See Stuart’s Opinionated Style Guide for Clojure Namespace Declarations for more recommendations.
State
Use let
instead of def
for local state
def
, defn
etc. should only ever be used as a top level form (barring few exceptions such as comment
and troubleshooting during REPL-driven development). Even if you use them inside a function, they still create globally visible Vars. Use let
for local state inside a function.
Functions
Use recur
for recursive calls to avoid blowing the stack
Clojure lacks (for good reasons and due to JVM limitations) automatic tail-call optimization (TCO) and therefore calling a function from its body runs into the risk of StackOverflowError
when recursing too many times. Use recur
(perhaps together with loop
) to explicitly mark the call for TCO and avoid the risk.
See the “Loop recur” ClojureBridge article for details.
Prefer higher-level sequence functions over low-level recursion
We rarely use recursion directly in Clojure because we can often achieve the desired result on a higher level by leveraging existing sequence functions, often reduce
. (That themselves are implemented with recursion.) It makes for a clearer code.
(defn exp [x n]
(loop [res 1, pow n]
(case pow
0 1
1 res
(recur
(* res x)
(dec pow)))))
;; =>
(defn exp [x n] (reduce * (repeat n x)))
Prefer =
over ==
In Clojure it is most common to use just =
for equality comparison. Only use ==
if you really need it. Even if you come from JavaScript :-).
An example when this might be useful is when comparing a BigDecimal and a constant to avoid the need to manually wrap the constant with (bigec)
(Clojure will do it for you).
Trivialities
Too much code on a single line
Don’t be afraid to split a line for readability, to visually separate individual important arguments. Otherwise it might be difficult to spot the individual arguments in the flood of the text. Ex.:
(reduce (fn [a b] (+ a (exp b (first res)))) 0 (rest res)))
;; =>
(reduce
(fn [a b] (+ a (exp b (first res))))
0
(rest res)))
Overusing “private”
If you come from Java, you are used to making everything private. In Clojure we use defn-
and ^private
sometimes but much less than in Java. Clojure targets trustworthy developers and allows them to shoot themselves into their foot if they really want to. Think about who do you want to protect from what with the privacy setting and whether it is necessary - especially if the function is essentially generic data transformation function. It is common to keep functions that are implementation details subject to change at any time and without notice into a separate *.impl.*
/,internal.*
namespace to mark them clearly as such and keep them public.
Public vs. Private Functions Clojure is biased toward making data and functions available by default. However, most namespaces have functions that are used as helpers or never intended to be part of the public usage. When you’re defining the functions in a namespace, consider how a consumer will perceive those functions and is expected to use it. Some tools and conventions are private vars, documentation strings, and the namespace structure itself. [..] You may find any or all of these techniques useful in indicating to users of your own code where to start
Not embracing the platform
Clojure is a hosted language and you are expected to leverage the host platform. Sometimes there are convenience Clojure wrappers but most often you are expected to use the JVM and Java libraries directly. Know and don’t hesitate to use methods such as Integer/parseInt
, Character/digit
etc. In particular, do not misuse read-string
for parsing.
Additional resources
Check out Stuart Sierra’s Clojure Do’s and Don’ts
Read the unofficial Clojure style guide