Clojure: Common beginner mistakes (WIP)
- Namespace declaration
recurfor recursive calls to avoid blowing the stack
- Prefer higher-level sequence functions over low-level recursion
- Use functions directly in map/filter/…
- Not embracing the platform
- Additional resources
Common mistakes made by Clojure beginners and style recommendations.
Use kebab-case-names, not camelCase or something else (unless you have some interop reasons)
End predicates with
Don’t add unnecessary prefixes to function names, such as
get-(this comes likely from the Java Bean standard and has no place here) or
compute-(most functions compute something!). Use
digitsfor fn that turns a number into a sequence of digits,
armstrong-numfor fn that computes the Armstrong Number, and
armstrong?for fn that tells you whether a number is an Armstrong number. See also Stuart Sierra’s great How to Name Clojure Functions.
(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.
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.
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.
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)))
In Clojure it is most common to use just
= for equality comparison. Only use
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).
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)))
If you come from Java, you are used to making everything private. In Clojure we use
^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
,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
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
Character/digit etc. In particular, do not misuse
read-string for parsing.
Are you benefitting from my writing? Consider buying me a coffee or supporting my work via GitHub Sponsors. Thank you! You can also book me for a mentoring / pair-programming session via Codementor or (cheaper) email.