Clojure Design Idiomsλ︎
An idiom is the Clojure way
Idioms are not Design Patterns found in languages like C++ and Java. Design patterns have a very specific language and structure and carefully design the context in which they are used.
Idioms are very simple in comparision, they are the way Clojure should be used.
Idiomatic Clojure is a term commonly used, commonly as a question. Is this idiomatic Clojure?
Idioms from style guideλ︎
A (WIP) refactor of the idioms from the Clojure Style guide
Dynamic Namespace Manipulationλ︎
TODO: vauge statement with no example
Avoid the use of namespace-manipulating functions like require
and
refer
. They are entirely unnecessary outside of a REPL
environment.
Forward Referencesλ︎
TODO: vauge statement with no example
Avoid forward references. They are occasionally necessary, but such occasions are rare in practice.
Declareλ︎
Use declare
to enable forward references when forward references are
necessary.
Higher-order Functionsλ︎
TODO: expand and provide examples
Prefer higher-order functions like map
to loop/recur
.
Appropriate Function useλ︎
- let def
- if when cond condp
- Nil Punning (a pun that null is not a value, when it is??) seq empty?
Use let for local namesλ︎
let
should be used inside a function definition or to create a local name when experimenting in the REPL.
def
defines a symbol name at the scope of the current namespace and therefore is inappropriate to use within a function definition.
Avoid vars inside functions
(defn iterative-function
"Avoid breaking the scope of data in a function by using def"
[args]
(def x 5) ; Avoid using a var as it breaks the scope of the function
...)
x ; evaluating will return the result without going through the function call and could be used anywere the namespace is required, increasing complexity.
Problematic debuging technique
An obscure debug technique is to define a def inside a function definition to capture intermediate values, however, this breaks the scope of Clojure and can lead to incorrect use of Clojure (especially by those new to the language).
Use a debug tool rather than break the scope of Clojure with a def, e.g. Cider Debug or Flowstorm-debugger.
Shadowing clojure.core
Names [[dont-shadow-clojure-core]]λ︎
Don't shadow clojure.core
names with local bindings or custom function definitions (confusion, a change of well held expectations).
RELATED: avoid renaming functions required by name via the ns
definition.
Alter Var Binding [[alter-var]]λ︎
Use alter-var-root
instead of def
to change the value of a var.
[source,clojure]
;; good
(def thing 1) ; value of thing is now 1
; do some stuff with thing
(alter-var-root #'thing (constantly nil)) ; value of thing is now nil
;; bad
(def thing 1)
; do some stuff with thing
(def thing nil)
; value of thing is now nil
Nil Punning [[nil-punning]]λ︎
Use seq
as a terminating condition to test whether a sequence is
empty (this technique is sometimes called nil punning).
[source,clojure]
;; good
(defn print-seq [s]
(when (seq s)
(prn (first s))
(recur (rest s))))
;; bad
(defn print-seq [s]
(when-not (empty? s)
(prn (first s))
(recur (rest s))))
Converting Sequences to Vectors [[to-vector]]λ︎
Prefer vec
over into
when you need to convert a sequence into a vector.
[source,clojure]
Converting Something to Booleanλ︎
Use the boolean
function if you need to convert something to an actual boolean value (true
or false
).
[source,clojure]
NOTE: Don't forget that the only values in Clojure that are "falsey" are false
and nil
. Everything else
will evaluate to true
when passed to the boolean
function.
You'll rarely need an actual boolean value in Clojure, but it's useful to know how to obtain one when you do.
when
vs if
[[when-instead-of-single-branch-if]]λ︎
Use when
instead of if
with just the truthy branch, as in (if condition (something...))
or (if ... (do ...))
.
[source,clojure]
if-let
[[if-let]]λ︎
Use if-let
instead of let
+ if
.
[source,clojure]
;; good
(if-let [result (foo x)]
(something-with result)
(something-else))
;; bad
(let [result (foo x)]
(if result
(something-with result)
(something-else)))
when-let
[[when-let]]λ︎
Use when-let
instead of let
+ when
.
[source,clojure]
;; good
(when-let [result (foo x)]
(do-something-with result)
(do-something-more-with result))
;; bad
(let [result (foo x)]
(when result
(do-something-with result)
(do-something-more-with result)))
if-not
[[if-not]]λ︎
Use if-not
instead of (if (not ...) ...)
.
[source,clojure]
when-not
[[when-not]]λ︎
Use when-not
instead of (when (not ...) ...)
.
[source,clojure]
when-not
vs if-not
[[when-not-instead-of-single-branch-if-not]]λ︎
Use when-not
instead of (if-not ... (do ...))
.
[source,clojure]
not=
[[not-equal]]λ︎
Use not=
instead of (not (= ...))
.
[source,clojure]
printf
[[printf]]λ︎
Prefer printf
over (print (format ...))
.
[source,clojure]
Flexible Comparison Functionsλ︎
When doing comparisons, leverage the fact that Clojure's functions <
,
>
, etc. accept a variable number of arguments.
[source,clojure]
Single Parameter Function Literal [[single-param-fn-literal]]λ︎
Prefer %
over %1
in function literals with only one parameter.
[source,clojure]
Multiple Parameters Function Literal [[multiple-params-fn-literal]]λ︎
Prefer %1
over %
in function literals with more than one parameter.
[source,clojure]
No Useless Anonymous Functions [[no-useless-anonymous-fns]]λ︎
Don't wrap functions in anonymous functions when you don't need to.
[source,clojure]
No Multiple Forms in Function Literals [[no-multiple-forms-fn-literals]]λ︎
Don't use function literals if the function body will consist of more than one form.
[source,clojure]
;; good
(fn [x]
(println x)
(* x 2))
;; bad (you need an explicit do form)
#(do (println %)
(* % 2))
Anonymous Functions vs complement
, comp
and partial
λ︎
Prefer anonymous functions over complement
, comp
and partial
, as this results
in simpler code most of the time.footnote:[You can read more on the subject https://ask.clojure.org/index.php/8373/when-should-prefer-comp-and-partial-to-anonymous-functions[here].]
= complement
[[complement]]λ︎
[source,clojure]
= comp
[[comp]]λ︎
[source,clojure]
;; Assuming `(:require [clojure.string :as str])`...
;; good
(map #(str/capitalize (str/trim %)) ["top " " test "])
;; okish
(map (comp str/capitalize str/trim) ["top " " test "])
comp
is quite useful when composing transducer chains, though.
[source,clojure]
= partial
[[partial]]λ︎
[source,clojure]
Threading Macros [[threading-macros]]λ︎
Prefer the use of the threading macros +->+
(thread-first) and +->>+
(thread-last) to heavy form nesting.
[source,clojure]
;; good
(-> [1 2 3]
reverse
(conj 4)
prn)
;; not as good
(prn (conj (reverse [1 2 3])
4))
;; good
(->> (range 1 10)
(filter even?)
(map (partial * 2)))
;; not as good
(map (partial * 2)
(filter even? (range 1 10)))
Threading Macros and Optional Parenthesesλ︎
Parentheses are not required when using the threading macros for functions having no argument specified, so use them only when necessary.
[source,clojure]
;; good
(-> x fizz :foo first frob)
;; bad; parens add clutter and are not needed
(-> x (fizz) (:foo) (first) (frob))
;; good, parens are necessary with an arg
(-> x
(fizz a b)
:foo
first
(frob x y))
Threading Macros Alignmentλ︎
The arguments to the threading macros +->+
(thread-first) and +->>+
(thread-last) should line up.
[source,clojure]
Default cond
Branch [[else-keyword-in-cond]]λ︎
Use :else
as the catch-all test expression in cond
.
[source,clojure]
;; good
(cond
(neg? n) "negative"
(pos? n) "positive"
:else "zero")
;; bad
(cond
(neg? n) "negative"
(pos? n) "positive"
true "zero")
condp
vs cond
[[condp]]λ︎
Prefer condp
instead of cond
when the predicate & expression don't
change.
[source,clojure]
;; good
(cond
(= x 10) :ten
(= x 20) :twenty
(= x 30) :thirty
:else :dunno)
;; much better
(condp = x
10 :ten
20 :twenty
30 :thirty
:dunno)
case
vs cond/condp
[[case]]λ︎
Prefer case
instead of cond
or condp
when test expressions are
compile-time constants.
[source,clojure]
;; good
(cond
(= x 10) :ten
(= x 20) :twenty
(= x 30) :forty
:else :dunno)
;; better
(condp = x
10 :ten
20 :twenty
30 :forty
:dunno)
;; best
(case x
10 :ten
20 :twenty
30 :forty
:dunno)
Short Forms In Cond [[short-forms-in-cond]]λ︎
Use short forms in cond
and related. If not possible give visual
hints for the pairwise grouping with comments or empty lines.
[source,clojure]
;; good
(cond
(test1) (action1)
(test2) (action2)
:else (default-action))
;; ok-ish
(cond
;; test case 1
(test1)
(long-function-name-which-requires-a-new-line
(complicated-sub-form
(-> 'which-spans multiple-lines)))
;; test case 2
(test2)
(another-very-long-function-name
(yet-another-sub-form
(-> 'which-spans multiple-lines)))
:else
(the-fall-through-default-case
(which-also-spans 'multiple
'lines)))
Set As Predicate [[set-as-predicate]]λ︎
Use a set
as a predicate when appropriate.
[source,clojure]
;; good
(remove #{1} [0 1 2 3 4 5])
;; bad
(remove #(= % 1) [0 1 2 3 4 5])
;; good
(count (filter #{\a \e \i \o \u} "mary had a little lamb"))
;; bad
(count (filter #(or (= % \a)
(= % \e)
(= % \i)
(= % \o)
(= % \u))
"mary had a little lamb"))
inc
and dec
[[inc-and-dec]]λ︎
Use (inc x)
& (dec x)
instead of (+ x 1)
and (- x 1)
.
pos?
and neg?
[[pos-and-neg]]λ︎
Use (pos? x)
, (neg? x)
& (zero? x)
instead of (> x 0)
,
(< x 0)
& (= x 0)
.
list*
vs cons
[[list-star-instead-of-nested-cons]]λ︎
Use list*
instead of a series of nested cons
invocations.
[source,clojure]
Sugared Java Interop [[sugared-java-interop]]λ︎
Use the sugared Java interop forms.
;;; object creation
;; good
(java.util.ArrayList. 100)
;; bad
(new java.util.ArrayList 100)
;;; static method invocation
;; good
(Math/pow 2 10)
;; bad
(. Math pow 2 10)
;;; instance method invocation
;; good
(.substring "hello" 1 3)
;; bad
(. "hello" substring 1 3)
;;; static field access
;; good
Integer/MAX_VALUE
;; bad
(. Integer MAX_VALUE)
;;; instance field access
;; good
(.someField some-object)
;; bad
(. some-object someField)
Compact Metadata Notation For True Flags [[compact-metadata-notation-for-true-flags]]λ︎
Use the compact metadata notation for metadata that contains only
slots whose keys are keywords and whose value is boolean true
.
[source,clojure]
Privateλ︎
Denote private parts of your code.
[source,clojure]
;; good
(defn- private-fun [] ...)
(def ^:private private-var ...)
;; bad
(defn private-fun [] ...) ; not private at all
(defn ^:private private-fun [] ...) ; overly verbose
(def private-var ...) ; not private at all
Access Private Var [[access-private-var]]λ︎
To access a private var (e.g. for testing), use the @#'some.ns/var
form.
Attach Metadata Carefully [[attach-metadata-carefully]]λ︎
Be careful regarding what exactly you attach metadata to.
[source,clojure]